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.*.*(..))" ,这样切点才定位到方法上了。