Spring学习4:Spring AOP(面向切面编程)
常用术语
学习AOP之前,先了解AOP中常用术语
- 切面(Aspect)
切面是一个关注点的模块化,如事务管理就是一个在JavaEE企业中应用最常见的界面。在企业应用编程中,首先需要通过分析,抽取出通用的功能,即“切面”。
事务、日志、安全性的框架、权限都是切面 - 连接点(Joinpoint)
连接点是执行程序过程中的特定的点。Spring框架只支持方法作为连接点,如方法调用之前、方法调用后、或者发生异常时等。 - 通知(Advice)
通知就是切面的具体实现。通知将在切面的某个特定的连接点上执行动作,Spring中执行的动作往往就是调用某一个类的具体方法。例如:在保存订单的模块中,进行日志管理(一个切面),具体是在保存订单的方法执行之前(连接点)执行写日志(通知)的功能。其中,日志管理是很多模块中通用的功能,因此这就是一个切面,而具体是在保存订单之前执行日志保存,那么保存订单前这个点就是连接点,实现日志保存功能的类就是通知。
就是切面中的方法。 切入点(Pointcut)
切入点是连接点的集合,通知将在满足一个切入点表达式的所有连接点上运行。
举例:
在拦截器中,有一系列判断性的内容1
if(method.equals("savePerson")||method.equals("updatePerson") ||method.equals("deletePerson")){ ... }
满足了上面三个方法才能开启事务,这些判断条件就为切入点
- 引入(Introduction)
引入的意思是在一个类中加入新的属性或者方法。 - 目标对象(Target Object)
被一个或多个切面所通知的对象成为目标对象。 - AOP代理(AOP Proxy)
AOP代理是AOP框架所生成的对象,该对象是目标对象的代理对象。代理对象能够在目标对象的基础上,在相应的连接点上调用通知。 - 织入(Weaving)
把切面连接到其他的应用程序之上,创建一个被通知的对象的过程,被称为织入。AOP两种实现模式
xml形式
举例说明如何在applicationContext.xml中配置AOP(因为当前只负责介绍AOP实现模式,因此配置环境需要的jar包先不介绍)举例说明
创建接口personDAO和实现类personDaoImpl1
2
3
4
5
6public interface PersonDao {
/**
* 目标方法
*/
public void savePerson();
}
1 | public class PersonDaoImpl implements PersonDao{ |
事务处理类Transaction1
2
3
4
5
6
7
8
9public class Transaction {
public void beginTransaction(){
System.out.println("begin transaction");
}
public void commit(){
System.out.println("commit");
}
}
接下来最重要的就是AOP的配置文件,applicationContext.xml。通过配置AOP可以实现创建代理对象:代理对象的方法=目标方法+通知。将原本不相关的目标方法和通知结合起来。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<bean id="personDao" class="cn.zju.spring.PersonDaoImpl"></bean>
<bean id="transaction" class="cn.zju.spring.Transaction"></bean>
<aop:config>
<!--
切入点表达式
确定目标类
id 标示
-->
<aop:pointcut expression="execution(* cn.zju.spring.PersonDaoImpl.*(..))" id="perform"/>
<aop:aspect ref="transaction"> <!-- ref指向切面类 -->
<aop:before method="beginTransaction" pointcut-ref="perform"/> <!--前置通知 -->
<aop:after-returning method="commit" pointcut-ref="perform"/> <!--后置通知 -->
</aop:aspect>
</aop:config>
客户端代码1
2
3
4
5public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
PersonDao personDao = (PersonDao)context.getBean("personDao");
personDao.savePerson();
}
实现原理
- 当启动Spring容器时,因为声明了两个bean,
… 所以Spring容易会为这两个bean创建对象。 - 当Spring解析到aop:config的时候,解析切入点表达式,让切入点表达式和Spring容器中的bean做匹配
- 如果匹配成功则为该类创建代理对象:代理对象的方法=目标方法+通知
- 在客户端通过getBean在Spring容器中查找相应对象,如果有代理对象则返回代理对象,否则返回对象本身。
切入点表达式
Spring AOP中用户可能会经常使用execution切入点指示符。执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
除了返回类型模式(上面代码片断中的ret-type-pattern),名字模式和参数模式以外, 所有的部分都是可选的。返回类型模式决定了方法的返回类型必须依次匹配一个连接点。 你会使用的最频繁的返回类型模式是*,它代表了匹配任意的返回类型。举例说明如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17execution(public * *(..))
任意的公共方法
execution(* set*(..))
以set开头的所有的方法
execution(* com.xyz.service.AccountService.*(..))
com.xyz.service.AccountService这个类里的所有的方法
execution(* com.xyz.service.*.*(..))
com.xyz.service包下的所有的类的所有的方法
execution(* com.xyz.service..*.*(..))
com.xyz.service包及子包下所有的类的所有的方法
excution(* cn.itheima06.spring.aop..*.*(String,?,Long))
cn.itheima06.spring.aop包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法
AOP中的各种通知
- 前置通知
在目标方法执行之前执行
通知中有一个参数JoinPoint,可以获取目标方法的一些信息 - 后置通知
在目标方法执行之后执行
可以获取目标方法的返回值
如果目标方法遇到异常,将不执行 - 异常通知
获取目标方法抛出的异常信息 - 最终通知:相当于代码中的finally
- 环绕通知:
能控制目标方法的执行,如果做权限控制,可以在这里进行判断。
举例说明,在上述例子中,我们修改事务类transaction和配置文件来实现各种通知的说明,切面类和目标方法都不需要做改变,
事务处理类transaction1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49public class Transaction {
/**
* 前置通知
* JoinPoint连接点
*/
public void beginTransaction(JoinPoint joinPoint){
System.out.println("args:"+joinPoint.getArgs());//获取目标方法的参数
//获取方法的名称
System.out.println("methodName:"+joinPoint.getSignature().getName());
System.out.println("begin transaction");
}
/**
* 后置通知
* @param joinPoint
* @param val
*/
public void commit(JoinPoint joinPoint,Object val){
System.out.println(val);
System.out.println("commit");
}
/**
* 异常通知
*/
public void throwingMethod(JoinPoint joinPoint,Throwable ex){
System.out.println(ex.getMessage());
}
/**
* 最终通知
*/
public void finallyMethod(){
System.out.println("finally method");
}
/**
* 环绕通知
* 控制目标方法的执行
*/
public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable{
String methodName = joinPoint.getSignature().getName();//获取目标方法的名称
if(methodName.equals("savePerson")){
joinPoint.proceed();//调用目标方法
}else{
System.out.println("权限不足");
}
}
}
接下来最重要的就是AOP的配置文件,applicationContext.xml,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53<bean id="personDao" class="cn.zju.spring.PersonDaoImpl"></bean>
<bean id="transaction" class="cn.zju.spring.Transaction"></bean>
<aop:config>
<!--
切入点表达式
确定目标类
id标示
-->
<aop:pointcut expression="execution(* cn.zju.spring.PersonDaoImpl.*(..))" id="perform"/>
<aop:aspect ref="transaction"><!-- ref指向切面类 -->
<!--
前置通知
1、在目标方法执行之前完成
2、有一个参数Joinpoint
该参数能够获取目标方法的一些信息
-->
<!--
<aop:before method="beginTransaction" pointcut-ref="perform"/>
-->
<!--
后置通知
1、在目标方法执行之后完成
2、有一个参数Joinpoint
3、returning属性可以获取目标方法的返回值
4、如果目标方法遇到异常,则后置通知将不再执行
-->
<!--
<aop:after-returning method="commit" pointcut-ref="perform" returning="val"/>
-->
<!--
异常通知
throwing
参数的名称 ex
获取目标方法抛出的异常信息
-->
<!--
<aop:after-throwing method="throwingMethod" pointcut-ref="perform" throwing="ex"/>
-->
<!--
最终通知
无论目标方法是否遇到异常,最终通知都要执行
-->
<!--
<aop:after method="finallyMethod" pointcut-ref="perform"/>
-->
<!--
环绕通知
-->
<aop:around method="aroundMethod" pointcut-ref="perform"/>
</aop:aspect>
</aop:config>
在后续博客中会有实际案例,通过案例可以更加清楚各种的通知的用法。
注解形式
在上述例子中,我们修改事务类transaction和配置文件来实现各种通知的说明,切面类和目标方法都不需要做改变,
事务处理类transaction1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48public class Transaction {
@Pointcut("execution(* cn.zju.spring.PersonDaoImpl.*(..))")
private void anyMethod(){}//定义一个切入点
@Before("anyMethod() && args(joinPoint)")
public void beginTransaction(JoinPoint joinPoint){
System.out.println("args:"+joinPoint.getArgs());//获取目标方法的参数
//获取方法的名称
System.out.println("methodName:"+joinPoint.getSignature().getName());
System.out.println("begin transaction");
}
@AfterReturning("anyMethod()")
public void commit(JoinPoint joinPoint,Object val){
System.out.println(val);
System.out.println("commit");
}
/**
* 异常通知
*/
@AfterThrowing("anyMethod()")
public void throwingMethod(JoinPoint joinPoint,Throwable ex){
System.out.println(ex.getMessage());
}
/**
* 最终通知
*/
@After("anyMethod()")
public void finallyMethod(){
System.out.println("finally method");
}
/**
* 环绕通知
* 控制目标方法的执行
*/
@Around("anyMethod()")
public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable{
String methodName = joinPoint.getSignature().getName();//获取目标方法的名称
if(methodName.equals("savePerson")){
joinPoint.proceed();//调用目标方法
}else{
System.out.println("权限不足");
}
}
}
然后下面的配置文件就比较简单了1
2
3
4
5
6
7
8
9
10
11
12
13
14<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<aop:aspectj-autoproxy/>
<bean id="personDao" class="cn.zju.spring.PersonDaoImpl"></bean>
<bean id="transaction" class="cn.zju.spring.Transaction"></bean>
</beans>