三AOP配置
Posted 啄木鸟伍迪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了三AOP配置相关的知识,希望对你有一定的参考价值。
一、AOP的配置(注解)
步骤一、导入jar包:
处理那5个jar包之外,还需要导入:
- aopalliance
- aspectjweaver
- spring-aop
- spring-aspects
步骤二、在配置文件中加入aop、context的命名空间
步骤三分为基于注解方式配置AOP和xml方式配置aop;
基于注解方式(本篇)
①在配置文件中加入如下配置;
1 <!-- 使AspjectJ注释起作用,自动匹配的类生成代理对象 --> 2 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
②把横切关注点的代码都加入到切面的类中,
③切面首先是一个IOC中的bean,即加@Conponent注释
④切面需要加入@Aspect注释
⑤在类中声明各种通知:
- @Before:前置通知,在方法执行前执行;
- @After:后置通知,在方法执行后执行
-
@AfterRunning:返回通知,在方法返回结果后执行
-
@Afterthrowing:异常通知之后
-
@Around:环绕通知
二、AOP常用通知:
- @Before:前置通知,在方法执行前执行;
@Before("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )") // 作用于接口中的所有方法 public void beforeMethod(JoinPoint joinPoint) { String method = joinPoint.getSignature().getName();// 方法的签名 List<Object> args = Arrays.asList(joinPoint.getArgs());// 方法的参数 System.out.println("the method " + method + " begins with" + args); }
- @After:后置通知,在方法执行后执行,在后置通知中,不能访问目标方法执行的结果,如果有异常也执行,通知在异常之前;
1 @After("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )") 2 public void afterMethod(JoinPoint joinPoint) { 3 String method = joinPoint.getSignature().getName(); 4 List<Object> args = Arrays.asList(joinPoint.getArgs()); 5 System.out.println("the method " + method + " is end to " + args); 6 }
-
@AfterRunning:返回通知,在方法返回结果后执行,可以访问到返回值;
1 @AfterReturning(value = "execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.add(int, int) )", returning = "result") 2 public void afterReturn(JoinPoint joinPoint, Object result) { 3 String method = joinPoint.getSignature().getName(); 4 System.out.println("the method " + method + " is end with " + result); 5 }
-
@Afterthrowing:异常通知之后,可以访问到异常,并且可以指定异常类型,只有符合该异常类型时才被执行
1 @AfterThrowing(value = "execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.add(int, int) )", throwing = "ex") 2 public void afterThrowing(JoinPoint joinPoint, Object ex) { 3 String method = joinPoint.getSignature().getName(); 4 System.out.println("the method " + method + " occured exception: " + ex); 5 }
-
@Around:环绕通知;
环绕通知类似动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行日志方法 且必须有返回值,返回值是目标方法的返回值
1 @Around("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.add(int, int) )") 2 public Object aroundMethod(ProceedingJoinPoint point) { 3 Object result = null; 4 String method = point.getSignature().getName(); 5 // 执行目标方法 6 try { 7 // 前置通知 8 System.out.println("the method " + method + " is begin with " + Arrays.asList(point.getArgs())); 9 result = point.proceed(); 10 // 返回通知 11 System.out.println("the method " + method + " is end to " + result); 12 } catch (Throwable e) { 13 // TODO Auto-generated catch block 14 System.out.println("the method " + method + " occured exception: " + e); 15 throw new RuntimeException(e); 16 } 17 System.out.println("the method " + method + " ends"); 18 19 return 100; 20 }
三、切点表达式
表达式
1 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下:
- modifiers-pattern:方法的可见性,如public,protected;
- ret-type-pattern:方法的返回值类型,如int,void等;
- declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
- name-pattern:方法名类型,如buisinessService();
- param-pattern:方法的参数类型,如java.lang.String;
- throws-pattern:方法抛出的异常类型,如java.lang.Exception;
举例说明:
// @Before("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.add(int, int) )")// 作用于接口中的add方法 // @Before("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )") // 作用于接口中的所有方法 (所有方法是指public int类型的) // @Before("execution(* ixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )") // 第一个* 表示:任意修饰符和任意返回值,第二个*代码任意参数为(int,int)方法 // @Before("execution( public * ixiuming.spring.aop.impl.ArithmeticCaculator.*(..) )") // 第二个*代表任意方法;..代表任意个数的参数,即所有公有方法 // @Before("execution( public double ixiuming.spring.aop.impl.ArithmeticCaculator.*(double.) )") // 返回所有double的第一个参数为double的public的方法
@Pointcut
使用@Pointcut 来声明切入点表达式,后面的其他通知直接使用方法名来引用当前的切入点表达式;如下代码,前置通知使用了方法名为declareJoinPointExpress来引用切点表达式;
这样做的好处是,可以统一管理切点表达式;
1 @Pointcut("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(..))") 2 public void declareJoinPointExpress() { 3 4 } 5 6 //声明该方法是一个前置通知:在目标方法之前执行 7 @Before("declareJoinPointExpress()") 8 public void beforeMethod(JoinPoint joinPoint) { 9 String method = joinPoint.getSignature().getName(); 10 List<Object> args = Arrays.asList(joinPoint.getArgs()); 11 System.out.println("the method " + method + " begins with" + args); 12 }
四、实例说明AOP配置:
以实现三(一)中的 为ArithmeticCaculator添加 各方法 执行前 和计算结果后的日志的AOP方案为实例;
步骤一、为ArithmeticCaculatorImpl类添加@Component 注解 来表示 该组件需要被sping容器管理
1 package lixiuming.spring.aop.impl; 2 3 import org.springframework.stereotype.Component; 4 5 @Component 6 public class ArithmeticCaculatorImpl2 implements ArithmeticCaculator { 7 8 @Override 9 public int add(int i, int j) { 10 int result = i+j; 11 return result; 12 } 13 14 @Override 15 public int sub(int i, int j) { 16 int result = i-j; 17 return result; 18 } 19 20 @Override 21 public int mul(int i, int j) { 22 int result = i*j; 23 return result; 24 } 25 26 @Override 27 public int div(int i, int j) { 28 int result = i/j; 29 return result; 30 } 31 32 }
步骤二、需要添加一个切面:
关于切面声明的说明:
- 切面需要放置在spring 容器中;所以首先需要一个@Component注解
- 声明一个切面用注解 @Aspect;
为实现上述实例,需要添加一个前置通知和后置通知;前置通知即,在目标方法执行之前执行;后置通知,即在目标方法执行后执行,无论是否发生异常。
1 package lixiuming.spring.aop.impl; 2 3 import java.util.Arrays; 4 import java.util.List; 5 6 import org.aspectj.lang.JoinPoint; 7 import org.aspectj.lang.annotation.After; 8 import org.aspectj.lang.annotation.Aspect; 9 import org.aspectj.lang.annotation.Before; 10 import org.springframework.stereotype.Component; 11 12 //把这个类声明为一个切面,需要把该类放入到IOC容器中,再声明为一个切面 13 @Aspect 14 @Component 15 public class LoggingAspect { 16 // 声明该方法是一个前置通知:在目标方法之前执行 18 @Before("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )") // 作用于接口中的所有方法 22 public void beforeMethod(JoinPoint joinPoint) { 23 String method = joinPoint.getSignature().getName();// 方法的签名 24 List<Object> args = Arrays.asList(joinPoint.getArgs());// 方法的参数 25 System.out.println("the method " + method + " begins with" + args); 26 } 27 28 // 声明后置通知:在目标方法执行后(无论是否发生异常)执行的通知 29 // 在后置通知中,不能访问目标方法执行的结果,需要在返回通知里面访问 30 @After("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(int, int) )") 31 public void afterMethod(JoinPoint joinPoint) { 32 String method = joinPoint.getSignature().getName(); 33 List<Object> args = Arrays.asList(joinPoint.getArgs()); 34 System.out.println("the method " + method + " is end to " + args); 35 } 36 37 }
步骤三、配置文件:引入了context和aop的命名空间
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd 8 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> 9 10 <context:component-scan base-package="lixiuming.spring.aop.impl"/> 11 <!-- 使AspjectJ注释起作用,自动匹配的类生成代理对象 --> 12 <aop:aspectj-autoproxy></aop:aspectj-autoproxy> 13 </beans>
使用main方法测试:
1 public static void main(String[] args) { 2 ApplicationContext cxt = new ClassPathXmlApplicationContext("ApplicationContext.xml"); 3 ArithmeticCaculator arithmeticCaculator = cxt.getBean(ArithmeticCaculator.class); 4 5 int result = arithmeticCaculator.add(1, 2); 6 System.out.println("-->" + result); 7 8 int result1 = arithmeticCaculator.div(4, 2); 9 System.out.println("-->" + result1); 10 11 int result2 = arithmeticCaculator.mul(4, 2); 12 System.out.println("-->" + result2); 13 14 int result3 = arithmeticCaculator.sub(4, 2); 15 System.out.println("-->" + result3); 16 17 }
测试运行结果:
the method add begins with[1, 2]
the method add is end to [1, 2]
-->3
the method div begins with[4, 2]
the method div is end to [4, 2]
-->2
the method mul begins with[4, 2]
the method mul is end to [4, 2]
-->8
the method sub begins with[4, 2]
the method sub is end to [4, 2]
-->2
步骤二中除了使用前置和后置通知,还可以使用环绕通知来实现上述功能;
代码如下:
1 package lixiuming.spring.aop.impl; 2 3 import java.util.Arrays; 4 5 import org.aspectj.lang.ProceedingJoinPoint; 6 import org.aspectj.lang.annotation.Around; 7 import org.aspectj.lang.annotation.Aspect; 8 import org.aspectj.lang.annotation.Pointcut; 9 import org.springframework.stereotype.Component; 10 11 //把这个类声明为一个切面,需要把该类放入到IOC容器中 12 @Aspect 13 @Component 14 public class LoggingAspect { 15 16 // 定义一个方法,用于声明切入点表达式,一般该方法中不需要其他的代码 17 // 使用@Pointcut 来声明切入点表达式, 18 // 后面的其他通知直接使用方法名来引用当前的切入点表达式 19 @Pointcut("execution(public int lixiuming.spring.aop.impl.ArithmeticCaculator.*(..))") 20 public void declareJoinPointExpress() { 21 22 } 23 24 /** 25 * 环绕通知需要携带ProceedingJoinPoint类型的参数 26 * 环绕通知类似动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行日志方法 且必须有返回值,返回值是目标方法的返回值 27 */ 28 @Around("declareJoinPointExpress()") 29 public Object aroundMethod(ProceedingJoinPoint point) { 30 Object result = null; 31 String method = point.getSignature().getName(); 32 // 执行目标方法 33 try { 34 // 前置通知 35 System.out.println("the method " + method + " is begin with " + Arrays.asList(point.getArgs())); 36 result = point.proceed(); 37 // 后置通知 38 System.out.println("the method " + method + " is end to " + Arrays.asList(point.getArgs())); 39 } catch (Throwable e) { 40 // TODO Auto-generated catch block 41 System.out.println("the method " + method + " occured exception: " + e); 42 throw new RuntimeException(e); 43 } 44 45 return result; 46 } 47 }
五、切面的优先级
使用@Order(index)指定执行顺序的优先级,index为数字,index越小,优先级越高;@Order位置为放置在@Aspect前面;代码如下:
@Order(1)//执行顺序的优先级 @Aspect @Component //验证通知 public class VlidationAspect { @Before("LoggingAspect.declareJoinPointExpress()") public void validationArgs(JoinPoint jointPoint){ System.out.println("-->validation:"+Arrays.asList(jointPoint.getArgs())); } }
六、xml方式配置AOP
ArithmeticCaculator 不变;ArithmeticCaculatorImpl移除@Component;
LoggingAspect:
1 package lixiuming.spring.aop.impl2; 2 3 import java.util.Arrays; 4 import java.util.List; 5 6 import org.aspectj.lang.JoinPoint; 7 import org.aspectj.lang.ProceedingJoinPoint; 8 import org.aspectj.lang.annotation.After; 9 import org.aspectj.lang.annotation.AfterReturning; 10 import org.aspectj.lang.annotation.AfterThrowing; 11 import org.aspectj.lang.annotation.Around; 12 import org.aspectj.lang.annotation.Aspect; 13 import org.aspectj.lang.annotation.Before; 14 import org.aspectj.lang.annotation.Pointcut; 15 import org.springframework.stereotype.Component; 16 17 public class LoggingAspect { 18 19 public void beforeMethod(JoinPoint joinPoint){ 20 String method = joinPoint.getSignature().getName(); 21 List<Object> args = Arrays.asList(joinPoint.getArgs()); 22 System.out.println("the method "+method+" begins with"+args); 23 } 24 25 public void afterMethod(JoinPoint joinPoint){ 26 String method = joinPoint.getSignature().getName(); 27 List<Object> args = Arrays.asList(joinPoint.getArgs()); 28 System.out.println("the method "+method+" is end to "+args); 29 } 30 31 /** 32 *在方法正常结束后执行的代码 33 *返回通知是可以访问到方法的返回值 34 */ 35 public void afterReturn(JoinPoint joinPoint,Object result){ 36 String method = joinPoint.getSignature().getName(); 37 System.out.println("the method "+method+" is end with " +result); 38 } 39 40 public void afterThrowing(JoinPoint joinPoint,Object ex){ 41 String method = joinPoint.getSignature().getName(); 42 System.out.println("the method "+method+" occured exception: " + ex); 43 } 44 45 public Object aroundMethod(ProceedingJoinPoint point){ 46 Object result = null; 47 String method = point.getSignature().getName(); 48 //执行目标方法 49 try { 50 //前置通知 51 System.out.println("the method "+method+" is begin with "+Arrays.asList(point.getArgs())); 52 result = point.proceed(); 53 //返回通知 54 System.out.println("the method "+method+" is end to "+ result); 55 } catch (Throwable e) { 56 // TODO Auto-generated catch block 57 System.out.println("the method "+method+" occured exception: " + e); 58 throw new RuntimeException(e); 59 } 60 System.out.println("the method "+method+" ends"); 61 62 return 100; 63 } 64 }
VlidationAspect:
1 package lixiuming.spring.aop.impl2; 2 3 import java.util.Arrays; 4 5 import org.aspectj.lang.JoinPoint; 6 import org.aspectj.lang.annotation.Aspect; 7 import org.aspectj.lang.annotation.Before; 8 import org.springframework.core.annotation.Order; 9 import org.springframework.stereotype.Component; 10 //验证通知 11 public class VlidationAspect { 12 public void validationArgs(JoinPoint jointPoint){ 13 System.out.println("-->validation:"+Arrays.asList(jointPoint.getArgs())); 14 } 15 16 17 }
配置文件:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 6 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> 7 8 <!-- 配置bean --> 9 <bean id="aop" class="lixiuming.spring.aop.impl2.ArithmeticCaculatorImpl2"></bean> 10 <!-- 配置切面的bean --> 11 <bean id="LoggingAspect" class="lixiuming.spring.aop.impl2.LoggingAspect"></bean> 12 <bean id="VlidationAspect" class="lixiuming.spring.aop.impl2.VlidationAspect"></bean> 13 14 <!-- 配置AOP --> 15 <aop:config> 16 <!-- 配置切面表达式 --> 17 <aop:pointcut expression="execution(* lixiuming.spring.aop.impl2.ArithmeticCaculator.*(int,int))" id="pointcut"/> 18 <!-- 配置切面及通知 --> 19 <aop:aspect ref="LoggingAspect" order="2"> 20 <aop:before method="beforeMethod" pointcut-ref="pointcut"/> 21 <aop:after method="afterMethod" pointcut-ref="pointcut"/> 22 <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/> 23 <aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="result"/> 24 <aop:around method="aroundMethod" pointcut-ref="pointcut"/> 25 </aop:aspect> 26 27 <aop:aspect ref="VlidationAspect" order="1"> 28 <aop:before method="validationArgs" pointcut-ref="pointcut"/> 29 </aop:aspect> 30 </aop:config> 31 </beans>
三:Spring-AOP源码
目录
相关demo代码下载:
my-spring.rar-Spring-AOP源码Demo
AOP, AspectJ, Spring AOP 前世今生
我们先来把它们的概念和关系说说清楚,我们学习的Spring-AOP其实冰山一角,但是AOP还有很多的相关内容需要了解。
- AOP 要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。
- AOP 的实现并不是因为 Java 提供了什么神奇的钩子,可以把方法的几个生命周期告诉我们,而是我们要实现一个代理,实际运行的实例其实是生成的代理类的实例。
- 作为 Java 开发者,我们都很熟悉 AspectJ 这个词,甚至于我们提到 AOP 的时候,想到的往往就是 AspectJ,即使你可能不太懂它是怎么工作的。这里,我们把 AspectJ 和 Spring AOP 做个简单的对比:
Spring AOP:
AOP 术语解释
https://www.processon.com/view/link/5ecca5ebe0b34d5f262eae3a
Spring Aop:
- 它基于动态代理来实现。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现。大家一定要明白背后的意思,包括什么时候会不用 JDK 提供的动态代理,而用 CGLIB 实现。
- Spring 3.2 以后,spring-core 直接就把 CGLIB 和 ASM 的源码包括进来了,这也是为什么我们不需要显式引入这两个依赖
- Spring 的 IOC 容器和 AOP 都很重要,Spring AOP 需要依赖于 IOC 容器来管理。
- Spring AOP 只能作用于 Spring 容器中的 Bean,它是使用纯粹的 Java 代码实现的,只能作用于 bean 的方法。
- Spring 提供了 AspectJ 的支持,但只用到的AspectJ的切点解析和匹配。
- 很多人会对比 Spring AOP 和 AspectJ 的性能,Spring AOP 是基于代理实现的,在容器启动的时候需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 那么好。
AspectJ:
- AspectJ 出身也是名门,来自于 Eclipse 基金会,link:https://www.eclipse.org/aspectj
- 属于静态织入,它是通过修改代码来实现的,它的织入时机可以是:
-
- Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
- Post-compile weaving:编译后织入,也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
- Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。
- AspectJ 能干很多 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案。Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入),而不是力求成为一个像 AspectJ 一样的 AOP 编程完全解决方案。
- 因为 AspectJ 在实际代码运行前完成了织入,所以大家会说它生成的类是没有额外运行时开销的。
Spring AOP
首先要说明的是,这里介绍的 Spring AOP 是纯的 Spring 代码,和 AspectJ 没什么关系,但是 Spring 延用了 AspectJ 中的概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。
后面介绍的如 @Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。
下面我们来介绍 Spring AOP 的使用方法,先从最简单的配置方式开始说起,这样读者想看源码也会比较容易。
目前 Spring AOP 一共有三种配置方式,Spring 做到了很好地向下兼容,所以大家可以放心使用。
- Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的,想看源码的同学可以从这里起步。
- Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间 <aop></aop>
- Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做 @AspectJ,但是这个和 AspectJ 其实没啥关系。
Spring 1.2 中的配置
这节我们将介绍 Spring 1.2 中的配置,这是最古老的配置,但是由于 Spring 提供了很好的向后兼容,以及很多人根本不知道什么配置是什么版本的,以及是否有更新更好的配置方法替代,所以还是会有很多代码是采用这种古老的配置方式的(比如声明式事务),这里说的古老并没有贬义的意思。
下面用一个简单的例子来演示怎么使用 Spring 1.2 的配置方式。
首先定义需要被增强的类:
接口:Calculate.java, 实现类:TulingCalculate.java
/**
* 计算类接口
* Created by xsls on 2019/6/10.
*/
public interface Calculate {
/**
* 加法
* @param numA
* @param numB
* @return
*/
int add(int numA, int numB);
/**
* 减法
* @param numA
* @param numB
* @return
*/
int sub(int numA, int numB);
/**
* 除法
* @param numA
* @param numB
* @return
*/
int div(int numA, int numB);
/**
* 乘法
* @param numA
* @param numB
* @return
*/
int multi(int numA, int numB);
int mod(int numA, int numB);
}
/**
* Created by xsls on 2019/6/10.
*/
@Component
public class TulingCalculate implements Calculate {
public int add(int numA, int numB) {
System.out.println("执行目标方法:add");
System.out.println(1/0);
return numA+numB;
}
public int sub(int numA, int numB) {
System.out.println("执行目标方法:reduce");
return numA-numB;
}
public int div(int numA, int numB) {
System.out.println("执行目标方法:div");
return numA/numB;
}
public int multi(int numA, int numB) {
System.out.println("执行目标方法:multi");
return numA*numB;
}
public int mod(int numA,int numB){
System.out.println("执行目标方法:mod");
int retVal = ((Calculate)AopContext.currentProxy()).add(numA,numB);
//int retVal = this.add(numA,numB);
return retVal%numA;
//return numA%numB;
}
接下来,我们定义 advice或Interceptor, 我这里提供2个:
advice 是我们接触的第一个概念,记住它是干什么用的。
TulingLogAdvice.java
public class TulingLogAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
String methodName = method.getName();
System.out.println("执行目标方法【"+methodName+"】的<前置通知>,入参"+ Arrays.asList(args));
}
}
TulingLogInterceptor.java
/***
* @Slogan 致敬大师,致敬未来的你
*/
public class TulingLogInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println(getClass()+"调用方法前");
Object ret=invocation.proceed();
System.out.println(getClass()+"调用方法后");
return ret;
}
}
上面的两个 Advice 分别用于方法调用前输出参数和方法调用后输出结果。
现在可以开始配置了,通过配置类:
FactoryBean方式创建单个代理:
// 被代理对象
@Bean
public Calculate tulingCalculate() {
return new TulingCalculate();
}
// Advice 方式
@Bean
public TulingLogAdvice tulingLogAdvice(){
return new TulingLogAdvice();
}
// Interceptor方式 , 类似环绕通知
@Bean
public TulingLogInterceptor tulingLogInterceptor() {
return new TulingLogInterceptor();
}
/**
* FactoryBean方式单个: ProxyFactoryBean
* @return
*/
@Bean
public ProxyFactoryBean calculateProxy(){
ProxyFactoryBean userService=new ProxyFactoryBean();
userService.setInterceptorNames("tulingLogAdvice","tulingLogInterceptor"); // 根据指定的顺序执行
userService.setTarget(tulingCalculate());
return userService;
}
接下来,我们跑起来看看:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(EalyAopMainConfig.class);
Calculate calculateProxy = ctx.getBean("calculateProxy",Calculate.class);
calculateProxy.div(1,1);
}
查看输出结果:
从结果可以看到,使用了责任链方式对advice和Interceptor都进行调用。这个例子理解起来应该非常简单,就是通过调用FactoryBean的getObject方法创建一个代理实现。
代理模式需要一个接口(可选)、一个具体实现类,然后就是定义一个代理类,用来包装实现类,添加自定义逻辑,在使用的时候,需要用代理类来生成实例。
此中方法有个致命的问题,如果我们只能指定单一的Bean的AOP, 如果多个Bean需要创建多个ProxyFactoryBean 。而且,我们看到,我们的拦截器的粒度只控制到了类级别,类中所有的方法都进行了拦截。接下来,我们看看怎么样只拦截特定的方法。
在上面的配置中,配置拦截器的时候,interceptorNames 除了指定为 Advice,是还可以指定为 Interceptor 和 Advisor 的。
这里我们来理解 Advisor 的概念,它也比较简单,它内部需要指定一个 Advice,Advisor 决定该拦截哪些方法,拦截后需要完成的工作还是内部的 Advice 来做。
它有好几个实现类,这里我们使用实现类 NameMatchMethodPointcutAdvisor 来演示,从名字上就可以看出来,它需要我们给它提供方法名字,这样符合该配置的方法才会做拦截。
@Bean
public NameMatchMethodPointcutAdvisor tulingLogAspect() {
NameMatchMethodPointcutAdvisor advisor=new NameMatchMethodPointcutAdvisor();
// 通知(Advice) :是我们的通知类
// 通知者(Advisor):是经过包装后的细粒度控制方式。
advisor.setAdvice(tulingLogAdvice());
advisor.setMappedNames("div");
return advisor;
}
/**
* FactoryBean方式单个: ProxyFactoryBean
* 控制粒度到方法
* @return
*/
@Bean
public ProxyFactoryBean calculateProxy(){
ProxyFactoryBean userService=new ProxyFactoryBean();
userService.setInterceptorNames("tulingLogAspect");
userService.setTarget(tulingCalculate());
return userService;
}
我们可以看到,calculateProxy这个 bean 配置了一个 advisor,advisor 内部有一个 advice。advisor 负责匹配方法,内部的 advice 负责实现方法包装。
注意,这里的 mappedNames 配置是可以指定多个的,用逗号分隔,可以是不同类中的方法。相比直接指定 advice,advisor 实现了更细粒度的控制,因为在这里配置 advice 的话,所有方法都会被拦截。
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(EalyAopMainConfig.class);
Calculate calculateProxy = ctx.getBean("calculateProxy",Calculate.class);
calculateProxy.div(1,1);
}
输出结果如下,只有 div方法被拦截:
上面,我们介绍完了 Advice、Advisor、Interceptor 三个概念,相信大家应该很容易就看懂它们了。
它们有个共同的问题,那就是我们得为每个 bean 都配置一个代理,之后获取 bean 的时候需要获取这个代理类的 bean 实例(如 ctx.getBean("calculateProxy",Calculate.class)),这显然非常不方便,不利于我们之后要使用的自动根据类型注入。下面介绍 autoproxy 的解决方案。
autoproxy:从名字我们也可以看出来,它是实现自动代理,也就是说当 Spring 发现一个 bean 需要被切面织入的时候,Spring 会自动生成这个 bean 的一个代理来拦截方法的执行,确保定义的切面能被执行。
这里强调自动,也就是说 Spring 会自动做这件事,而不用像前面介绍的,我们需要显式地指定代理类的 bean。
我们去掉原来的 ProxyFactoryBean 的配置,改为使用 BeanNameAutoProxyCreator 来配置:
/**
*autoProxy: BeanPostProcessor手动指定Advice方式 BeanNameAutoProxyCreator
* @return
*/
@Bean
public BeanNameAutoProxyCreator autoProxyCreator() {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
//设置要创建代理的那些Bean的名字
beanNameAutoProxyCreator.setBeanNames("tuling*");
//设置拦截链名字(这些拦截器是有先后顺序的)
beanNameAutoProxyCreator.setInterceptorNames("tulingLogInterceptor");
return beanNameAutoProxyCreator;
}
配置很简单,beanNames 中可以使用正则来匹配 bean 的名字来增强多个类。 也就是说不再是配置某个 bean 的代理了。
注意,这里的 InterceptorNames 和前面一样,也是可以配置成 Advisor 和 Interceptor 的。
然后我们修改下使用的地方:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(EalyAopMainConfig.class);
Calculate tulingCalculate = ctx.getBean("tulingCalculate",Calculate.class);
tulingCalculate.div(1,1);
}
发现没有,我们在使用的时候,完全不需要关心代理了,直接使用原来的类型就可以了,这是非常方便的。
输出结果就是 OrderService 和 UserService 中的每个方法都得到了拦截:
到这里,是不是发现 BeanNameAutoProxyCreator 非常好用,它需要指定被拦截类名的模式(如 *ServiceImpl),它可以配置多次,这样就可以用来匹配不同模式的类了。
另外,在 BeanNameAutoProxyCreator 同一个包中,还有一个非常有用的类 DefaultAdvisorAutoProxyCreator,比上面的 BeanNameAutoProxyCreator 还要方便。
之前我们说过,advisor 内部包装了 advice,advisor 负责决定拦截哪些方法,内部 advice 定义拦截后的逻辑。所以,仔细想想其实就是只要让我们的 advisor 全局生效就能实现我们需要的自定义拦截功能、拦截后的逻辑处理。
BeanNameAutoProxyCreator 是自己匹配方法,然后交由内部配置 advice 来拦截处理;
而 DefaultAdvisorAutoProxyCreator 是让 ioc 容器中的所有 advisor 来匹配方法,advisor 内部都是有 advice 的,让它们内部的 advice 来执行拦截处理。
1、我们需要再回头看下 Advisor 的配置,上面我们用了 NameMatchMethodPointcutAdvisor 这个类:
/**
*autoProxy: BeanPostProcessor手动指定Advice方式 BeanNameAutoProxyCreator
* @return
*/
@Bean
public BeanNameAutoProxyCreator autoProxyCreator() {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
//设置要创建代理的那些Bean的名字
beanNameAutoProxyCreator.setBeanNames("tuling*");
//设置拦截链名字(这些拦截器是有先后顺序的)
beanNameAutoProxyCreator.setInterceptorNames("tulingLogInterceptor");
return beanNameAutoProxyCreator;
}
其实 Advisor 还有一个更加灵活的实现类 RegexpMethodPointcutAdvisor,它能实现正则匹配,如:
// RegexpMethodPointcutAdvisor 按正则匹配类
@Bean
public RegexpMethodPointcutAdvisor tulingLogAspectInterceptor() {
RegexpMethodPointcutAdvisor advisor=new RegexpMethodPointcutAdvisor();
advisor.setAdvice(tulingLogInterceptor());
advisor.setPattern("tuling.TulingCalculate.*");
return advisor;
}
也就是说,我们能通过配置 Advisor,精确定位到需要被拦截的方法,然后使用内部的 Advice 执行逻辑处理。
2、之后,我们需要配置 DefaultAdvisorAutoProxyCreator,它的配置非常简单,直接使用下面这段配置就可以了,它就会使得所有的 Advisor 自动生效,无须其他配置。(记得把之前的autoProxyCreator配置去掉,无需创建2次代理)
/**
* BeanPostProcessor自动扫描Advisor方式 DefaultAdvisorAutoProxyCreator
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator autoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
然后我们运行一下:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(EalyAopMainConfig.class);
Calculate tulingCalculate = ctx.getBean("tulingCalculate",Calculate.class);
tulingCalculate.div(1,1);
}
输出:
从结果可以看出,create 方法使用了 logArgsAdvisor 进行传参输出,query 方法使用了 logResultAdvisor 进行了返回结果输出。
到这里,Spring 1.2 的配置就要介绍完了。本文不会介绍得面面俱到,主要是关注最核心的配置,如果读者感兴趣,要学会自己去摸索,比如这里的 Advisor 就不只有我这里介绍的 NameMatchMethodPointcutAdvisor 和 RegexpMethodPointcutAdvisor,AutoProxyCreator 也不仅仅是 BeanNameAutoProxyCreator 和 DefaultAdvisorAutoProxyCreator。
读到这里,我想对于很多人来说,就知道怎么去阅读 Spring AOP 源码了。
Spring 2.0 @AspectJ 配置
Spring 2.0 schema-based 配置
请参考基础文档:05-SpringAOP的使用详解.note
AspectJ编译方式实现AOP:
AspectJ方式不多讲,2大核心:
- 定义了切面表达式的语法和解析机制
- 提供了强大的织入工具
它是通过织入的方式:直接将切面在【编译前、后】或【JVM加载的时候】进行织入到.class代码中。在实际生产中,我们用得最多的还是纯 Spring AOP,因为AspectJ学习成本高, Spring AOP已经能满足日常开发种的需求。 通过本AspectJ大家了解下 Spring Aop只用到了aspectj的设计理念(注解)和切点表达式配对。
AccountAspect .aj
public aspect AccountAspect {
pointcut callPay(int amount, Account account):
call(boolean cn.tulingxueyuan.myAspectJ.model.Account.pay(int)) && args(amount) && target(account);
before(int amount, Account account): callPay(amount, account) {
System.out.println("[AccountAspect]付款前总金额: " + account.balance);
System.out.println("[AccountAspect]需要付款: " + amount);
}
boolean around(int amount, Account account): callPay(amount, account) {
if (account.balance < amount) {
System.out.println("[AccountAspect]拒绝付款!");
return false;
}
return proceed(amount, account);
}
after(int amount, Account balance): callPay(amount, balance) {
System.out.println("[AccountAspect]付款后,剩余:" + balance.balance);
}
}
MainApp.java
public class MainApp {
public static void main(String[] args) {
testCompileTime();
}
public static void testCompileTime() {
Account account = new Account();
System.out.println("==================");
account.pay(10);
account.pay(50);
System.out.println("==================");
}
}
织入后:
public class MainApp {
public MainApp() {
}
public static void main(String[] args) {
testCompileTime();
}
public static void testCompileTime() {
Account account = new Account();
System.out.println("==================");
byte var1 = 10;
Account var2 = account;
try {
AccountAspect.aspectOf().ajc$before$cn_tulingxueyuan_myAspectJ_AccountAspect$1$ed770766(var1, var2);
pay_aroundBody1$advice(var2, var1, AccountAspect.aspectOf(), var1, var2, (AroundClosure)null);
} catch (Throwable var8) {
AccountAspect.aspectOf().ajc$after$cn_tulingxueyuan_myAspectJ_AccountAspect$3$fa1eb897(var1, account);
throw var8;
}
AccountAspect.aspectOf().ajc$after$cn_tulingxueyuan_myAspectJ_AccountAspect$3$fa1eb897(var1, account);
byte var4 = 50;
Account var5 = account;
try {
AccountAspect.aspectOf().ajc$before$cn_tulingxueyuan_myAspectJ_AccountAspect$1$ed770766(var4, var5);
pay_aroundBody3$advice(var5, var4, AccountAspect.aspectOf(), var4, var5, (AroundClosure)null);
} catch (Throwable var7) {
AccountAspect.aspectOf().ajc$after$cn_tulingxueyuan_myAspectJ_AccountAspect$3$fa1eb897(var4, account);
throw var7;
}
AccountAspect.aspectOf().ajc$after$cn_tulingxueyuan_myAspectJ_AccountAspect$3$fa1eb897(var4, account);
System.out.println("==================");
}
}
那AOP的4种实现方式就给大家总结到之里, 讲这块内容主要是为了减少待会大家阅读源码的障碍性。那么我们正式进入源码的领域:
spring aop源码解析
我们知道,spring中的aop是通过动态代理实现的,那么他具体是如何实现的呢?spring通过一个切面类,在他的类上加入@Aspect注解,定义一个Pointcut方法,最后定义一系列的增强方法。这样就完成一个对象的切面操作。
那么思考一下,按照上述的基础,要实现我们的aop,大致有以下思路:
- 找到所有的切面类
- 解析出所有的advice并保存
- 创建一个动态代理类
- 调用被代理类的方法时,找到他的所有增强器,并增强当前的方法
那么下面通过源码验证一下我们的猜测:
一、切面类的解析
spring通过@EnableAspectJAutoProxy开启aop切面,在注解类上面发现@Import(AspectJAutoProxyRegistrar.class),AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar,所以他会通过registerBeanDefinitions方法为我们容器导入beanDefinition。
进入解析切面的过程:
postProcessBeforeInstantiation是在任意bean创建的时候就调用了
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation
org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation
详细流程图:
https://www.processon.com/view/link/5f1958a35653bb7fd24d0aad
追踪一下源码可以看到最终导入AnnotationAwareAspectJAutoProxyCreator,我们看一下他的类继承关系图,发现它实现了两个重要的接口,BeanPostProcessor和InstantiationAwareBeanPostProcessor
首先看InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法
Object postProcessBeforeInstantiation(Class beanClass, String beanName)(InstantiationAwareBeanPostProcessor)
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation
org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#shouldSkip
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors
org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
public List<Advisor> buildAspectJAdvisors() {
//获取缓存中的aspectBeanNames
List<String> aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new ArrayList<>();
aspectNames = new ArrayList<>();
//获取beanFactory中所有的beanNames
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
for (String beanName : beanNames) {
if (!isEligibleBean(beanName)) {
continue;
}
// We must be careful not to instantiate beans eagerly as in this case they
// would be cached by the Spring container but would not have been weaved.
Class<?> beanType = this.beanFactory.getType(beanName);
if (beanType == null) {
continue;
}
//找出所有类上面含@Aspect注解的beanName
if (this.advisorFactory.isAspect(beanType)) {
//将找到的beanName放入aspectNames集合
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
//1.找到切面类的所有但是不包括@Pointcut注解的方法
//2.筛选出来包含@Around, @Before, @After,@ AfterReturning, @AfterThrowing注解的方法
//3.封装为List<Advisor>返回
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
//将上面找出来的Advisor按照key为beanName,value为List<Advisor>的形式存入advisorsCache
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}
if (aspectNames.isEmpty()) {
return Collections.emptyList();
}
List<Advisor> advisors = new ArrayList<>();
for (String aspectName : aspectNames) {
//当再次进入该方法,会直接从advisorsCache缓存中获取
List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
if (cachedAdvisors != null) {
advisors.addAll(cachedAdvisors);
}
else {
MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
return advisors;
}
流程图:
解析的步骤:
最终将解析出来的advisor放入缓存,这里思考清楚 advisor和advise的区别
其实就是我们切面中的通知方法:
二、创建代理
进入创建代理的过程:
postProcessAfterInitialization是在bean创建完成之后执行的
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
详细流程图:
https://www.processon.com/view/link/5f1e93f25653bb7fd2549b7c
1.获取advisors:创建代理之前首先要判断当前bean是否满足被代理, 所以需要将advisor从之前的缓存中拿出来和当前bean 根据表达式进行匹配:
Object postProcessAfterInitialization(@Nullable Object bean, String beanName)(BeanPostProcessor)
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary
org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors
上述代码的链路最终到了findCandidateAdvisors,我们发现在postProcessBeforeInstantiation方法中对查找到的Advisors做了缓存,所以这里只需要从缓存中取就好了
最后创建代理类,并将Advisors赋予代理类,缓存当前的代理类
2.匹配:根据advisors和当前的bean根据切点表达式进行匹配,看是否符合。
org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply
org.springframework.aop.support.AopUtils#findAdvisorsThatCanApply
org.springframework.aop.support.AopUtils#canApply(org.springframework.aop.Advisor, java.lang.Class, boolean) 拿到PointCut
org.springframework.aop.support.AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class, boolean)
org.springframework.aop.ClassFilter#matches 粗筛
org.springframework.aop.IntroductionAwareMethodMatcher#matches 精筛
3.创建代理:找到了 和当前Bean匹配的advisor说明满足创建动态代理的条件:
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
理解了上面两个重要的方法,我们只需要将他与创建bean的流程联系起来就可以知道代理对象创建的整个流程了,在before和after方法分别放置断点,我们可以看到他的整个调用链路
三、代理类的调用
https://www.processon.com/view/link/5f4dd513e0b34d1abc735998
前面的分析可知,spring将找到的增强器Advisors赋予了代理类,那么在执行只要将这些增强器应用到被代理的类上面就可以了,那么spring具体是怎么实现的呢,下面我们以jdk代理为例分析一下源码:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
//获取当前被代理类
TargetSource targetSource = this.advised.targetSource;
Object target = null;
// equals,hashcode等方法不做代理,直接调用
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
// 将代理对象放到线程本地变量中
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
//将增加器装换为方法执行拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
//将拦截器链包装为ReflectiveMethodInvocation并执行
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
通过上面代码可知,将增强器装换为方法拦截器链,最终包装为ReflectiveMethodInvocation执行它的proceed方法,那么我们就来看下具体如果执行
public Object proceed() throws Throwable {
// 当执行到最后一个拦截器的时候才会进入
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
//获取集合当前需要运行的拦截器
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// 执行拦截器方法
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
这样一看会感觉很蒙,其实追踪一下源码就很好理解了
org.springframework.aop.interceptor.ExposeInvocationInterceptor#invoke
public Object invoke(MethodInvocation mi) throws Throwable {
MethodInvocation oldInvocation = invocation.get();
invocation.set(mi);
try {
return mi.proceed();
}
finally {
invocation.set(oldInvocation);
}
}
org.springframework.aop.aspectj.AspectJAfterThrowingAdvice#invoke
异常拦截器,当方法调用异常会被执行
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (Throwable ex) {
if (shouldInvokeOnThrowing(ex)) {
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
throw ex;
}
}
org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor#invoke
返回拦截器,方法执行失败,不会调用
public Object invoke(MethodInvocation mi) throws Throwable {
Object retVal = mi.proceed();
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
org.springframework.aop.aspectj.AspectJAfterAdvice#invoke
后置拦截器,总是执行
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
finally {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor#invoke
前置拦截器
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
这里用了责任链的设计模式,递归调用排序好的拦截器链
以上是关于三AOP配置的主要内容,如果未能解决你的问题,请参考以下文章