Spring AOP 的实现是基于Java的代理机制,从JDK1.3开始就支持代理功能。
描述AOP常用的一些术语有通知(Adivce)、切点(Pointcut)、连接点(Join point)、切面(Aspect)、引入(Introduction)、织入(Weaving)
通知分为五中类型:
Before
在方法被调用之前调用
After
在方法完成后调用通知,无论方法是否执行成功
After-returning
在方法成功执行之后调用通知
After-throwing
在方法抛出异常后调用通知
Around
通知了好、包含了被通知的方法,在被通知的方法调用之前后调用之后执行自定义的行为
A point during the execution of a program, such as the execution of a method or the handling of an exception.
比如:方法调用、方法执行、字段设置/获取、异常处理执行、类初始化、甚至是 for 循环中的某个点
理论上, 程序执行过程中的任何时点都可以作为作为织入点, 而所有这些执行时点都是 Joint point
但 Spring AOP 目前仅支持方法执行 (method execution)
通知(advice)定义了切面何时,那么切点就是定义切面“何处” 描述某一类 Joint points, 比如定义了很多 Joint point, 对于 Spring AOP 来说就是匹配哪些方法的执行
描述方式:
直接指定 Jointpoint 所在的方法名, 功能比较单一, 通常只支持方法级别的 AOP 框架
特定的描述语言, 如 AspectJ 提供的 Pointcut 描述语言
切面是切点和通知的结合。通知和切点共同定义了关于切面的全部内容,它是什么时候,在何时和何处完成功能
引用允许我们向现有的类添加新的方法或者属性
组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
AspectJ
JBoss AOP
Spring AOP
Spring提供了四种各具特色的AOP支持
基于代理的经典AOP
@AspectJ 注解驱动的切面
纯POJO切面
注入式AspectJ切面(适合Spring各个版本)
前面三中都是Spring基于代理的AOP变体,因此,Spring对AOP的支持局限于方法拦截。如果AOP需求超过了简单的方法拦截范畴,那么应该考虑在ASpectJ里实现切面,利用Spring的DI把Spring BEan注入到ASpectJ切面中
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
重点关注动态代理技术
Spring的动态代理包括两个部分:
JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
CGLib全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。
Spring AOP 框架对 AOP 代理类的处理原则是:
如果目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类;
如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类——不过这个选择过程对开发者完全透明、开发者也无需关心。
Spring AOP 会动态选择使用 JDK 动态代理、CGLIB 来生成 AOP 代理,如果目标类实现了接口,Spring AOP 则无需 CGLIB 的支持,直接使用 JDK 提供的 Proxy 和 InvocationHandler 来生成 AOP 代理即可。
[](javascript:void(0)
1
2
21
22
23
24
25
26
27
[](javascript:void(0)
[](javascript:void(0)
1 import java.util.Arrays;
2 import org.aspectj.lang.JoinPoint;
3 import org.aspectj.lang.ProceedingJoinPoint;
4 import org.aspectj.lang.annotation.After;
5 import org.aspectj.lang.annotation.AfterReturning;
6 import org.aspectj.lang.annotation.AfterThrowing;
7 import org.aspectj.lang.annotation.Around;
8 import org.aspectj.lang.annotation.Aspect;
9 import org.aspectj.lang.annotation.Before;
10 import org.aspectj.lang.annotation.Pointcut;
11 import org.springframework.core.annotation.Order;
12 import org.springframework.stereotype.Component;
13
14 @Component
15 @Order(1)
16 @Aspect
17 public class TestAOP {
18
19 /**
20 * 切入点
21 */
22 @Pointcut("execution(* com.zhwy.misp.action.*.*(..))")
23 public void pointCut(){}
24
25 /**
26 * 前置通知
27 */
28 @Before(value = "pointCut()")
29 public void beforeMethod(JoinPoint joinPoint){
30 String methodName = joinPoint.getSignature().getName();
31 System.out.println("前置通知执行了,methodName:"+methodName);
32 }
33
34 /**
35 * 后置通知
36 */
37 @After("pointCut()")
38 public void afterMethod(JoinPoint joinPoint){
39 String methodName = joinPoint.getSignature().getName();
40 System.out.println("后置通知执行了,methodName:" + methodName);
41 }
42
43 /**
44 * 返回通知——可以访问到方法的返回值
45 */
46 @AfterReturning(value="pointCut()", returning="result")
47 public void afterReturnMethod(JoinPoint joinPoint, Object result){
48 String methodName = joinPoint.getSignature().getName();
49 System.out.println("返回通知执行了,methodName:" + methodName+result);
50 }
51
52 /**
53 * 异常通知——方法发生异常执行
54 * 可以访问到异常对象;且可以指定在出现特定异常时执行的代码
55 */
56 @AfterThrowing(value="pointCut()",throwing="ex")
57 public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
58 String methodName = joinPoint.getSignature().getName();
59 System.out.println("异常通知了,methodName:" + methodName + ex);
60 }
61 /**
62 * 环绕通知(需要携带类型为ProceedingJoinPoint类型的参数)
63 * 环绕通知包含前置、后置、返回、异常通知;ProceedingJoinPoin 类型的参数可以决定是否执行目标方法
64 * 且环绕通知必须有返回值,返回值即目标方法的返回值
65 */
66 @Around(value="pointCut()")
67 public Object aroundMethod(ProceedingJoinPoint point){
68 Object result = null;
69 String methodName = point.getSignature().getName();
70 try {
71 //前置通知
72 System.out.println("前置通知:"+ methodName + Arrays.asList(point.getArgs()));
73 //执行目标方法
74 result = point.proceed();
75 //返回通知
76 System.out.println("返回通知:"+ methodName + result);
77 } catch (Throwable ex) {
78 //异常通知
79 System.out.println("异常通知:"+methodName + ex);
80 throw new RuntimeException(ex);
81 }
82 //后置通知
83 System.out.println("后置通知:"+ methodName);
84 return result;
85 }
86 }
[](javascript:void(0)
@Before:前置通知,在方法前通知;
@After :后置通知,在方法执行后通知;
@AfterRunning:返回通知,在方法返回结果之后通知;
@AfterThrowing:异常通知,在方法抛出异常之后通知;
@Around:环绕通知,围绕着方法执行;
execution(* com.zhwy.misp.action..(…)) :第一个表示任意的修饰符(public/private/protected)及任意的返回值(void/Object);第二个第三个表示任意的方法,‘…’表示任意数量的参数;
execution(public * com.zhwy.misp.action.data.(…)):表示com.zhwy.misp.action包下以data结尾的公共的方法(public)的方法;
execution(public void com.zhwy.misp.action.data.(…)):表示com.zhwy.misp.action包下以data结尾的公共的方法(public)返回类型是void的方法;
execution(public void com.zhwy.misp.action.data.(int,…)):表示com.zhwy.misp.action包下以data结尾公共的方法(public)返回类型是void的类第一个参数是int的方法;
execution(public void com.zhwy.misp.action.data.(int,int)):表示com.zhwy.misp.action包下以data结尾公共的方法(public)返回类型是void的类第一个参数是int第二个参数是int的方法;
可以在方法中声明一个类型为JoinPoint的参数,然后就可以访问链接细节,如方法名称和参数值;
[](javascript:void(0)
1
2
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
[](javascript:void(0)
[](javascript:void(0)
1 import java.util.Arrays;
2 import org.aspectj.lang.JoinPoint;
3 import org.aspectj.lang.ProceedingJoinPoint;
4
5 public class TestAOP {
6
7 /**
8 * 前置通知
9 */
10 public void beforeMethod(JoinPoint joinPoint){
11 String methodName = joinPoint.getSignature().getName();
12 System.out.println("前置通知执行了,methodName:"+methodName);
13 }
14
15 /**
16 * 后置通知
17 */
18 public void afterMethod(JoinPoint joinPoint){
19 String methodName = joinPoint.getSignature().getName();
20 System.out.println("后置通知执行了,methodName:" + methodName);
21 }
22
23 /**
24 * 返回通知——可以访问到方法的返回值
25 */
26 public void afterReturnMethod(JoinPoint joinPoint, Object result){
27 String methodName = joinPoint.getSignature().getName();
28 System.out.println("返回通知执行了,methodName:" + methodName+result);
29 }
30
31 /**
32 * 异常通知——方法发生异常执行
33 * 可以访问到异常对象;且可以指定在出现特定异常时执行的代码
34 */
35 public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
36 String methodName = joinPoint.getSignature().getName();
37 System.out.println("异常通知了,methodName:" + methodName + ex);
38 }
39 /**
40 * 环绕通知(需要携带类型为ProceedingJoinPoint类型的参数)
41 * 环绕通知包含前置、后置、返回、异常通知;ProceedingJoinPoin 类型的参数可以决定是否执行目标方法
42 * 且环绕通知必须有返回值,返回值即目标方法的返回值
43 */
44 public Object aroundMethod(ProceedingJoinPoint point){
45 Object result = null;
46 String methodName = point.getSignature().getName();
47 try {
48 //前置通知
49 System.out.println("前置通知:"+ methodName + Arrays.asList(point.getArgs()));
50 //执行目标方法
51 result = point.proceed();
52 //返回通知
53 System.out.println("返回通知:"+ methodName + result);
54 } catch (Throwable ex) {
55 //异常通知
56 System.out.println("异常通知:"+methodName + ex);
57 throw new RuntimeException(ex);
58 }
59 //后置通知
60 System.out.println("后置通知:"+ methodName);
61 return result;
62 }
63 }
[](javascript:void(0)
如果要使用AspectJ完成注解切面需要注意下面的JDK与AspectJ的匹配:
JDK1.6 —— aspectJ1.6
JDK1.7 —— aspectJ1.7.3+
JDK1.8 —— aspectJ1.8+
Spring 切入点配置错误引起的
expression=“execution(* com.zhwy.misp.service..(…))”; 后面两个*,表示service包下的所有类下的所有方法
如果要使用AspectJ完成注解切面需要注意下面的JDK与AspectJ的匹配:
JDK1.6 —— aspectJ1.6
JDK1.7 —— aspectJ1.7.3+
JDK1.8 —— aspectJ1.8+
Spring 切入点配置错误引起的
expression=“execution(* com.zhwy.misp.service..(…))”; 后面两个*,表示service包下的所有类下的所有方法
expression=“execution(* com.zhwy.misp.service..(…))” ,这样切点才定位到方法上了。