Spring AOP

  Spring AOP 的实现是基于Java的代理机制,从JDK1.3开始就支持代理功能。

 

1.AOP的一些概念

  描述AOP常用的一些术语有通知(Adivce)、切点(Pointcut)、连接点(Join point)、切面(Aspect)、引入(Introduction)、织入(Weaving)

  • 通知(Advice)

  通知分为五中类型:

  Before
  在方法被调用之前调用
  After
  在方法完成后调用通知,无论方法是否执行成功
  After-returning
  在方法成功执行之后调用通知
  After-throwing
  在方法抛出异常后调用通知
  Around
  通知了好、包含了被通知的方法,在被通知的方法调用之前后调用之后执行自定义的行为

  • 连接点(Join point)

  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)

  • 切点(Pointcut)

  通知(advice)定义了切面何时,那么切点就是定义切面“何处” 描述某一类 Joint points, 比如定义了很多 Joint point, 对于 Spring AOP 来说就是匹配哪些方法的执行
  描述方式:
  直接指定 Jointpoint 所在的方法名, 功能比较单一, 通常只支持方法级别的 AOP 框架

  • 正则表达式

  特定的描述语言, 如 AspectJ 提供的 Pointcut 描述语言

  • 切面(Aspect)

  切面是切点和通知的结合。通知和切点共同定义了关于切面的全部内容,它是什么时候,在何时和何处完成功能

  • 引入(Introduction)

  引用允许我们向现有的类添加新的方法或者属性

  • 织入(Weaving)

  组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

 

2.Spring 对AOP的支持

  • AOP框架种类

AspectJ
JBoss AOP
Spring AOP
Spring提供了四种各具特色的AOP支持
基于代理的经典AOP
@AspectJ 注解驱动的切面
纯POJO切面
注入式AspectJ切面(适合Spring各个版本)
前面三中都是Spring基于代理的AOP变体,因此,Spring对AOP的支持局限于方法拦截。如果AOP需求超过了简单的方法拦截范畴,那么应该考虑在ASpectJ里实现切面,利用Spring的DI把Spring BEan注入到ASpectJ切面中

3.Spring实现方式

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
重点关注动态代理技术
Spring的动态代理包括两个部分:

  • JDK动态代理

JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。

Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

  • CGLib动态代理

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 代理即可。

 

4.AOP实例

1)导入aopalliance-1.0.jar、aspectjrt-1.7.4.jar、aspectjweaver-1.7.4.jar3个jar包
2)配置xml文件
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"  
 3     xmlns:mvc="http://www.springframework.org/schema/mvc"  
 4     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 5     xmlns:p="http://www.springframework.org/schema/p"  
 6     xmlns:context="http://www.springframework.org/schema/context"  
 7     xmlns:aop="http://www.springframework.org/schema/aop" 
 8     xmlns:tx="http://www.springframework.org/schema/tx" 
 9     xsi:schemaLocation="   
10            http://www.springframework.org/schema/beans   
11            http://www.springframework.org/schema/beans/spring-beans.xsd   
12            http://www.springframework.org/schema/context   
13            http://www.springframework.org/schema/context/spring-context.xsd  
14            http://www.springframework.org/schema/mvc   
15            http://www.springframework.org/schema/mvc/spring-mvc.xsd
16            http://www.springframework.org/schema/aop 
17            http://www.springframework.org/schema/aop/spring-aop.xsd
18            http://www.springframework.org/schema/tx 
19            http://www.springframework.org/schema/tx/spring-tx.xsd
20            ">                 
21  
22     <context:component-scan base-package="com.zhwy.misp"/>
23    
24     <!--aop-->
25     <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
26           
27     <mvc:default-servlet-handler />
  3)编写一个测试切面TestAOP.java
 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 }
  • 切面首先是IOC容器中的一个bean,即在切面类前加上标签@Component标签
  • 添加标签@Aspect标签,声明该类是一个切面
  • 实现需要的通知,在方法之前添加如下的通知类型:

@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的参数,然后就可以访问链接细节,如方法名称和参数值;

  5. 基于XML配置的AOP

  1)在spring配置文件中添加aop的命名空间,配置如下:
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"  
 3     xmlns:mvc="http://www.springframework.org/schema/mvc"  
 4     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 5     xmlns:p="http://www.springframework.org/schema/p"  
 6     xmlns:context="http://www.springframework.org/schema/context"  
 7     xmlns:aop="http://www.springframework.org/schema/aop" 
 8     xmlns:tx="http://www.springframework.org/schema/tx" 
 9     xsi:schemaLocation="   
10            http://www.springframework.org/schema/beans   
11            http://www.springframework.org/schema/beans/spring-beans.xsd   
12            http://www.springframework.org/schema/context   
13            http://www.springframework.org/schema/context/spring-context.xsd  
14            http://www.springframework.org/schema/mvc   
15            http://www.springframework.org/schema/mvc/spring-mvc.xsd
16            http://www.springframework.org/schema/aop 
17            http://www.springframework.org/schema/aop/spring-aop.xsd
18            http://www.springframework.org/schema/tx 
19            http://www.springframework.org/schema/tx/spring-tx.xsd
20            ">                 
21  
22     <context:component-scan base-package="com.zhwy.misp"/>
23    
24     <!--aop-->
25     <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
26     
27     <!-- 配置切面Bean -->
28     <bean id="testAOP" class="com.zhwy.misp.aop.TestAOP"></bean>
29     
30     <!-- 配置AOP -->
31     <aop:config>
32         <!-- 配置切点表达式  -->
33         <aop:pointcut id="pointcut" expression="execution(* com.zhwy.misp.action.*.*(..))" />
34         <!-- 配置切面及配置 -->
35         <aop:aspect order="1" ref="testAOP">
36             <!-- 前置通知 -->
37             <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
38             <!-- 后置通知 -->
39             <aop:after method="afterMethod" pointcut-ref="pointcut"/>
40             <!-- 返回通知 -->
41             <aop:after-returning method="afterReturnMethod" pointcut-ref="pointcut" returning="result"/>
42             <!-- 异常通知 -->
43             <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"/>
44         </aop:aspect>
45     </aop:config>
46     
  2)编写一个测试切面TestAOP.java
 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 }

6.AOP的坑

1)AspectJ报错:error at ::0 can't find referenced pointcut XXX

如果要使用AspectJ完成注解切面需要注意下面的JDK与AspectJ的匹配:

  JDK1.6 —— aspectJ1.6

  JDK1.7 —— aspectJ1.7.3+

  JDK1.8 —— aspectJ1.8+

2)错误: warning no match for this type name: com.zhwy.misp.service [Xlint:invalidAbsoluteTypeName]

Spring 切入点配置错误引起的

expression="execution(* com.zhwy.misp.service.*.*(..))";  后面两个*,表示service包下的所有类下的所有方法

expression="execution(* com.zhwy.misp.service.*.*(..))" ,这样切点才定位到方法上了。

原文地址:https://www.cnblogs.com/aaron911/p/7592508.html