[关闭]
@Catyee 2021-08-04T07:52:00.000000Z 字数 16970 阅读 594

源码解析Spring AOP

Spring


AOP即面向切面编程,AOP是一种编程范式,或者说是一种编程思想,并不局限于某一种具体的实现方式,但不管通过何种方式实现,其主要作用都是用于将外围业务代码与核心业务代码分离,减少重复代码,使代码结构更加清晰,提高开发效率

日志,权限,事务,性能监测等这些功能都属于通用的外围业务,将这些外围业务从核心业务中提取出来作为单独的模块,按照一定的规则重新织入到核心业务中,这样开发者只需要关注核心功能代码的开发,而不用重复的编写外围业务的代码,这就是AOP的作用。

// TODO
我们知道AOP只是一种编程思想,所以AOP并不局限于某一种实现方式。Spring AOP和AspectJ是最具有代表性的两种不同实现思路的AOP框架,Spring AOP是在代码运行时动态生成代理对象来完成建言的织入,但是除了代码运行时还可以通过操控java字节码在代码编译期、或者类加载期完成建言的织入,这就是AspectJ。

一、AOP思想体系中的一些术语

AOP的思想体系中其实有挺多术语的,但是很多看名字就能知道含义,所以这里只列举4个特别重要的,弄明白了这4个概念基本也就弄清楚了AOP。

1.1 连接点(JoinPoint)

表示在程序中明确定义的点,典型的包括方法调用,对象的初始化以及异常处理程序块的执行等等。简单来说连接点就是被拦截到的程序执行点,Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。

1.2 切点(PointCut)

切点是对连接点进行拦截的条件定义,Spring缺省使用AspectJ切点语法。

可以简单认为所有的方法都是是连接点,但我们并不希望在所有的方法上都织入外围业务的逻辑,所以就有了切点,切点的作用就是提供一组规则来匹配连接点,只有满足规则的连接点才会织入外围业务的逻辑

1.3 建言(Advice)

Advice即在连接点执行的代码,也就是外围业务的具体逻辑,Advice经常被译为"通知"或者"增强","通知"是直译,属于单词本身的意思,但是不能体现Advice在AOP中的作用。而"增强"属于意译,单词本身没有这个含义,但是在AOP中Advice属于一段功能增强的逻辑,意译也能说的过去,但是并不信达雅。我这里译作"建言",或者"提意",不管译作什么,含义都是一样的,都是指匹配到连接点之后要执行的代码。Spring中Advice从大的类别来说可以分为两类,一种是用切点来匹配连接点的建言(PointcutAdvice),这是典型的AOP,这种建言有分为5个小类:

PointcutAdvice大类之外另外一种是引入建言(IntroductionAdvice),所谓引入建言,就是指一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能,从功能上来说引入建言不像是典型的AOP,更像是动态代理使用方式的扩展,由于Spring AOP也使用动态代理来实现,所以在实现的时候将引入建言和AOP放在一起了。

1.4 切面(Aspect)

一个切面可以简单认为就是一个外围业务的模块,切面由建言(Advice)和切点(PointCut)组成,建言定义了外围业务的具体逻辑,切点则定义了这些外围业务的逻辑将织入到核心业务的哪些连接点中。在Spring中,可以简单地认为使用@Aspect注解的类就是切面。
有了切面的定义之后可以这样理解Spring AOP:Spring AOP是负责实施切面的框架,其目的就是将切面中所定义的横切逻辑(建言)织入到切面所指定的连接点中。

这里用Spring中的一个例子来理解AOP中的各种概念:
首先需要开启AOP功能,注意proxyTargetClass这个参数,默认是false,表示spring在能使用jdk动态代理的情况下优先选择jdk动态代理,如果是true,将默认使用cglib动态代理。

  1. @Configuration
  2. @ComponentScan({"spring.example.aop"}) // 定义Spring要扫描的包
  3. @EnableAspectJAutoProxy(proxyTargetClass = true) // 开启AOP
  4. public class AppConfig {
  5. }

BeanA是一个普通的Bean,BeanA中的5个方法都是AOP中的连接点。

  1. @Component
  2. public class BeanA {
  3. public void m1() {
  4. System.out.println("m1 invoked");
  5. }
  6. public void m2() {
  7. System.out.println("m2 invoked");
  8. }
  9. public String m3() {
  10. System.out.println("m3 invoked, return str");
  11. return "m3";
  12. }
  13. public void m4() throws Exception {
  14. System.out.println("m4 invoked");
  15. throw new Exception("m4 found exception");
  16. }
  17. public void m5() {
  18. System.out.println("m5 invoked");
  19. }
  20. }

MyAspect类上有@Aspect注解,所以是一个切面,这个切面中有5个advice,通过注解来区分advice的种类,注解的参数则定义了切点。比如@Before注解标注beforeAdvice方法是前置建言,@Before注解中参数execution(* spring.example.aop.BeanA.m1(..))就是切点的定义,意味这个前置建言只织入到spring.example.aop.BeanA这个bean的m1方法这个连接点中。

  1. @Aspect
  2. @Component
  3. public class MyAspect {
  4. // 前置建言
  5. @Before("execution(* spring.example.aop.BeanA.m1(..))")
  6. public void beforeAdvice(){
  7. System.out.println("before type advice invoked");
  8. }
  9. // 后置建言
  10. @After("execution(* spring.example.aop.BeanA.m2(..))")
  11. public void afterAdvice(){
  12. System.out.println("after type advice invoked");
  13. }
  14. // 后置返回建言
  15. @AfterReturning(returning="rvt", pointcut = "execution(* spring.example.aop.BeanA.m3(..))")
  16. public void afterReturningAdvice(Object rvt){
  17. System.out.println("after returning type advice invoked, rvt: " + rvt);
  18. }
  19. // 异常建言
  20. @AfterThrowing(throwing="ex", pointcut = "execution(* spring.example.aop.BeanA.m4(..))")
  21. // 声明ex时指定的类型要求目标方法必须抛出指定类型的异常
  22. // 此处将ex的类型声明为Throwable,意味着对目标方法抛出的异常不加限制
  23. public void afterThrowingAdvice(Throwable ex){
  24. System.out.println("after throwing type advice invoked, ex: " + ex.getMessage());
  25. }
  26. // 环绕建言
  27. @Around("execution(* spring.example.aop.BeanA.m5(..))")
  28. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  29. // 外围业务的逻辑环绕了核心业务
  30. System.out.println("around before invoked.");
  31. Object obj = joinPoint.proceed();
  32. System.out.println("around after invoked.");
  33. return obj;
  34. }
  35. }

主函数入口:

  1. public static void main(String[] args) {
  2. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  3. BeanA beanA = (BeanA) context.getBean("beanA");
  4. beanA.m1();
  5. System.out.println("==============");
  6. beanA.m2();
  7. System.out.println("==============");
  8. beanA.m3();
  9. System.out.println("==============");
  10. try {
  11. beanA.m4();
  12. } catch (Exception e) {
  13. // ignore
  14. }
  15. System.out.println("==============");
  16. beanA.m5();
  17. }

执行的结果:

  1. before type advice invoked
  2. m1 invoked
  3. ==============
  4. m2 invoked
  5. after type advice invoked
  6. ==============
  7. m3 invoked, return str
  8. after returning type advice invoked, rvt: m3
  9. ==============
  10. m4 invoked
  11. after throwing type advice invoked, ex: m4 found exception
  12. ==============
  13. around before invoked.
  14. m5 invoked
  15. around after invoked.

二、 提议者(Advisor)

上面列举了AOP中的一些重要概念,在谈论Spring AOP的时候我们还经常谈到另外一个概念就是Advisor,它看起来很像是AOP思想体系中的概念,但它其实是Spring AOP中独有的概念,AOP中并没有Advisor的概念。

那Spring AOP为什么要创造这样一个新的概念呢?是因为Spring对AOP做了功能扩展吗?实际上不是的,在第一章中给出了AOP的一个例子,不知大家发现没有,其实实现AOP最重要的就是要编写好外围业务的具体逻辑(建言),但是这还不够,我们还要规定这些外围业务应该作用于什么地方(匹配规则),换个说法就是当我们定义出一个建言之后,总是会再定义建言匹配连接点的规则。看到没有,建言和匹配规则总是成对出现的,那么在实现的时候就可以将建言和匹配规则封装在一起,定义成一个类方便操作,这个类就是Advisor,所以说Advisor本质是一个类,不是一个术语,只是刚好这个类的命名看起来像是AOP体系中的一个术语,导致很多人混淆,如果当时Spring AOP的开发者将其命名为AdviceWrapper,或许现在就不会引起混淆了。

之所以要单独拿出来就是要区分advisor这个概念,一定要记得advice、pointcut这些术语是aop体系中的概念,与具体实现无关;advisor正好相反,它不是aop体系中的概念,而是spring实现aop的过程中创建的一个类,与具体实现相关。所以advisor与aop体系中的概念不可相提并论。

刚刚提到Advisor是一个类,所以我们来从源码看Advisor是怎么定义的:

  1. /**
  2. * Base interface holding AOP <b>advice</b> (action to take at a joinpoint) and a filter determining the applicability of the advice (such as a pointcut)
  3. */
  4. public interface Advisor {
  5. Advice EMPTY_ADVICE = new Advice() {};
  6. Advice getAdvice();
  7. boolean isPerInstance();
  8. }

可以看到Advisor就是一个接口,看一下注释,注释中写的很清楚,Advisor就是对建言和建言匹配连接点规则的封装,这里大致翻译了一下:"Advisor是一个顶层接口,接口中定义了获取建言(建言就是在连接点执行的动作)的方法以及获取决定建言作用于何处的过滤器(比如切点)的方法"。注意注释中第二个括号里面的内容,这说明切点只是建言匹配连接点的多种方式中的其中一种。实际上Spring中有两种建言,一种是用切点来匹配连接点的建言(PointcutAdvice),另外一种是引入建言(IntroductionAdvice),也分别对应两种不同的Advisor:PointcutAdvisor和IntroductionAdvisor。

如下是PointcutAdvisor接口源码:

  1. // 切点建言的Advisor
  2. public interface PointcutAdvisor extends Advisor {
  3. /**
  4. * Get the Pointcut that drives this advisor.
  5. */
  6. Pointcut getPointcut();
  7. }

如下是IntroductionAdvisor接口的源码:

  1. // 引入建言的Advisor
  2. public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
  3. /**
  4. * Return the filter determining which target classes this introduction
  5. * should apply to.
  6. * <p>This represents the class part of a pointcut. Note that method
  7. * matching doesn't make sense to introductions.
  8. * @return the class filter
  9. */
  10. ClassFilter getClassFilter();
  11. void validateInterfaces() throws IllegalArgumentException;
  12. }

虽然有两种不同的advisor,但是它们的本质是一样的,都是对建言和建言匹配连接点规则的封装。在第一章中我们举了一个例子,通过注解定义了MyAspect这样一个切面,这个切面中有5个建言,五个建言各有不同的切点,那么按照上面的分析,这些成对的建言和切点最终应该被封装为PointcutAdvisor对象,我们来实际调试一下,看结论是不是真的如此。
我们知道Spring AOP是在bean后置处理器的后方法中生成代理类的,对应的bean后置处理器为AnnotationAwareAspectJAutoProxyCreator,在它的后置方法中打断点往下调试,结果如图:

切面类中的建言和切点如何存在

可以看到MyAspect这个切面中的5个建言和对应的切点都被封装为了Advisor,具体为InstantiationModelAwarePointcutAdvisorImpl,从名字也可以看出来它是PointcutAdvisor接口的一个具体实现,所以结果确实如我们所料,Spring确实将成对的建言和切点封装成了PointcutAdvisor对象,这也印证了我们前面的结论:"Advisor不是一个特殊的术语,只是Spring在实现AOP框架时对建言和建言匹配连接点规则的封装,是具体实现相关的概念"。

弄清楚了Advisor的概念之后,我们具体来分析切点建言和引入建言这两种建言的实现方式。

三、切点建言PointcutAdvice

在实际开发中我们要如何应用AOP呢?有两种方式,第一种就是通过注解来定义切面类,在切面类中定义建言和切点;第二种是我们直接继承AbstractPointcutAdvisor或者PointcutAdvisor类,在这个类中定义建言和切点。
在第一章中我们给出了注解定义切面类的例子,这里再举一个直接继承AbstractPointcutAdvisor来使用AOP的例子:

  1. @Component
  2. public class BeanB {
  3. // m6是连接点
  4. public void m6() {
  5. System.out.println("m6 invoked");
  6. }
  7. }

MyAdvisor直接继承AbstractPointcutAdvisor类用来定义切点和建言:

  1. @Component
  2. public class MyAdvisor extends AbstractPointcutAdvisor {
  3. @Override
  4. public Pointcut getPointcut() {
  5. // 定义切点,也就是BeanB的m6方法
  6. NameMatchMethodPointcut methodPointcut = new NameMatchMethodPointcut();
  7. methodPointcut.setClassFilter(clazz -> clazz == BeanB.class);
  8. methodPointcut.addMethodName("m6");
  9. return methodPointcut;
  10. }
  11. @Override
  12. public Advice getAdvice() {
  13. // 定义前置建言
  14. return new MethodBeforeAdvice() {
  15. @Override
  16. public void before(Method method, Object[] args, Object target) throws Throwable {
  17. System.out.println("before type advice in my advisor invoked.");
  18. }
  19. };
  20. }
  21. }

主函数入口:

  1. public static void main(String[] args) {
  2. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  3. BeanB beanB = (BeanB) context.getBean("beanB");
  4. beanB.m6();
  5. }

执行结果:

  1. before type advice in my advisor invoked
  2. m6 invoked

无论是使用注解还是直接继承,其底层实现都是殊途同归的,最终都通过Advisor来实现,所以可以放到一起来看。AOP是通过动态代理来实现的,提到动态代理就有代理类和代理方法,所以我们要搞清楚它们是如何生成代理类以及代理方法是如何执行的。

3.1 如何生成代理类

Spring AOP是在bean后置处理器的后方法中生成代理类的,对应的bean后置处理器为AnnotationAwareAspectJAutoProxyCreator,在它的后置方法中打断点进行调试,我这里制作了一个长图来展示生成代理类的调用过程,可以点开进行查看:生成代理对象的调用过程

整个调用过程最主要的逻辑在AbstractAutoProxyCreator类的warpIfNecessary方法中,在这个方法中分为两步:先获取到当前bean相关的advisor,然后构建代理类并创建代理对象。具体来说是先调用getAdvicesAndAdvisorsForBean方法用来找到与当前bean相关的所有advisor,包括从切面类中解析并构造的advisor以及直接继承PointcutAdvisor类的advisor,如果没有找到相关的advisor则说明不用动态代理,如果有在调用creatProxy方法,这个方法会构建处代理类并创建代理对象,spring会自动选择使用jdk动态代理还是cglib动态代理。

  1. protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
  2. ... // 省略部分代码
  3. // Create proxy if we have advice.
  4. // 获取与当前bean相关的所有advisor,包括切面类中构造处的advisor和直接继承PointcutAdvisor类的advisor
  5. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
  6. if (specificInterceptors != DO_NOT_PROXY) {
  7. this.advisedBeans.put(cacheKey, Boolean.TRUE);
  8. // 构建代理类并实例化代理对象。
  9. Object proxy = createProxy(
  10. bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
  11. this.proxyTypes.put(cacheKey, proxy.getClass());
  12. return proxy;
  13. }
  14. this.advisedBeans.put(cacheKey, Boolean.FALSE);
  15. return bean;
  16. }

3.1.1 获取与当前bean相关的所有advisor

getAdvicesAndAdvisorsForBean方法最终会调用到findEligibleAdvisors方法,在findEligibleAdvisors中又会调用到findCandidateAdvisors方法来获取所有的advisor,获取到所有advisor之后找到与当前bean相关的advisor,然后对这些advisor进行排序。

  1. protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
  2. // 调用findCandidateAdvisors方法获取所有advisor
  3. List<Advisor> candidateAdvisors = findCandidateAdvisors();
  4. // 在所有advisor中找到与当前bean相关的advisor
  5. List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
  6. extendAdvisors(eligibleAdvisors);
  7. if (!eligibleAdvisors.isEmpty()) {
  8. // 对获取到的advisor排序
  9. eligibleAdvisors = sortAdvisors(eligibleAdvisors);
  10. }
  11. return eligibleAdvisors;
  12. }

findCandidateAdvisors方法,在findCandidateAdvisors方法中先获取到直接继承PointcutAdvisor类的advisor,然后构建并获取切面类中的advisor:

  1. protected List<Advisor> findCandidateAdvisors() {
  2. // Add all the Spring advisors found according to superclass rules.
  3. // 获取直接继承PointcutAdvisor类的advisor
  4. List<Advisor> advisors = super.findCandidateAdvisors();
  5. // Build Advisors for all AspectJ aspects in the bean factory.
  6. if (this.aspectJAdvisorsBuilder != null) {
  7. // 构建并获取切面类中advisor
  8. advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
  9. }
  10. return advisors;
  11. }

由于直接继承PointcutAdvisor类的Advisor也是一个bean,所以Spring获取这些Advisor的时候直接走的是创建bean的逻辑。但对于切面类中的Advisor则不同,需要从切面类中解析出建言和切点,再构造出Advisor。

在获取到与当前bean相关的所有Advisor之后要进行排序,排序的方式是Spring中一贯的方式,Spring中所有需要排序的地方都是一样的,Spring定义了两个排序级别,一个是Ordered接口,一个是PriorityOrdered接口,后者本身继承自Ordered接口。

  1. // Ordered接口
  2. public interface Ordered {
  3. // 最高优先级
  4. int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
  5. // 最低优先级
  6. int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
  7. int getOrder();
  8. }
  9. // PriorityOrdered接口,未定义任何方法,只起到一个标记作用
  10. public interface PriorityOrdered extends Ordered {
  11. }

这两个接口用到的默认的比较器是OrderComparator,比较方法:

  1. public class OrderComparator implements Comparator<Object> {
  2. ... // 省略部分代码
  3. @Override
  4. public int compare(@Nullable Object o1, @Nullable Object o2) {
  5. return doCompare(o1, o2, null);
  6. }
  7. private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
  8. boolean p1 = (o1 instanceof PriorityOrdered);
  9. boolean p2 = (o2 instanceof PriorityOrdered);
  10. if (p1 && !p2) {
  11. return -1;
  12. }
  13. else if (p2 && !p1) {
  14. return 1;
  15. }
  16. int i1 = getOrder(o1, sourceProvider);
  17. int i2 = getOrder(o2, sourceProvider);
  18. return Integer.compare(i1, i2);
  19. }
  20. }

上面doCompare的逻辑是:如果两个对象中有一个对象实现了PriorityOrdered接口,那么这个对象的优先级更高;如果两个对象都是PriorityOrdered或Ordered接口的实现类,那么比较Ordered接口的getOrder方法得到order值,值越低,优先级越高;如果对象没有实现Ordered接口,那么就默认order值是int最大值,也就是最低优先级。这段逻辑之外默认如果两个对象的优先级一样,则按照字典序排序,比如两个对象都没有实现Ordered接口,order值都是int最大值,那就比较它们名字的字典序。

由于讲到了排序,所以这里就插讲一下Advice最终的执行顺序。

3.1.2 Advice的执行顺序

  1. @Aspect
  2. @Component
  3. public class MyAspect {
  4. // 前置建言
  5. @Before("execution(* spring.example.aop.BeanA.m1(..))")
  6. public void beforeAdvice(){
  7. System.out.println("before type advice invoked");
  8. }
  9. // 后置建言
  10. @After("execution(* spring.example.aop.BeanA.m1(..))")
  11. public void afterAdvice(){
  12. System.out.println("after type advice invoked");
  13. }
  14. // 后置返回建言
  15. @AfterReturning(returning="rvt", pointcut = "execution(* spring.example.aop.BeanA.m1(..))")
  16. public void afterReturningAdvice(Object rvt){
  17. System.out.println("after returning type advice invoked, rvt: " + rvt);
  18. }
  19. // 异常建言
  20. @AfterThrowing(throwing="ex", pointcut = "execution(* spring.example.aop.BeanA.m1(..))")
  21. // 声明ex时指定的类型要求目标方法必须抛出指定类型的异常
  22. // 此处将ex的类型声明为Throwable,意味着对目标方法抛出的异常不加限制
  23. public void afterThrowingAdvice(Throwable ex){
  24. System.out.println("after throwing type advice invoked, ex: " + ex.getMessage());
  25. }
  26. // 环绕建言
  27. @Around("execution(* spring.example.aop.BeanA.m1(..))")
  28. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  29. // 外围业务的逻辑环绕了核心业务
  30. System.out.println("around before invoked.");
  31. Object obj = joinPoint.proceed();
  32. System.out.println("around after invoked.");
  33. return obj;
  34. }
  35. }

然后再调用m1方法,执行结果如下:

  1. around before invoked.
  2. before type advice invoked
  3. m1 invoked
  4. after returning type advice invoked, rvt: null
  5. after type advice invoked
  6. around after invoked.

m1方法是没有抛异常的,所以异常建言没有执行,假如m1抛异常呢?执行结果如下:

  1. around before invoked.
  2. before type advice invoked
  3. m1 invoking
  4. after throwing type advice invoked, ex: m1 found exception
  5. after type advice invoked

可以看到在有抛出异常的时候环绕建议的后部分就不会执行了,原因也很简单:抛出异常中断了执行流程,环绕建言的后部分还没来的及执行。但是要注意后置建言还是能执行的。
总结两次执行的结果,可以得出结论:
如果连接点未抛异常,执行顺序是:around before -> before -> 目标方法(连接点) -> after returning -> after -> around after
如果连接点有抛异常,执行顺序是:around before -> before -> 目标方法(连接点) -> throwing -> after

根据结论,我们也可以这样理解不同建言执行的位置:

  1. public void invoke() {
  2. aroundBefore(); // 环绕建言的前部分
  3. before(); // 前置建言
  4. try {
  5. target(); // 目标方法
  6. afterReturning(); // 后置返回建言
  7. } catch (Exception e) {
  8. afterThrowing(); // 异常建言
  9. throw e;
  10. } finally {
  11. after(); // 后置建言
  12. }
  13. }
  1. @Aspect
  2. @Component
  3. public class MyAspect {
  4. @Before("execution(* spring.aop.BeanA.m1(..))")
  5. public void beforeAdvice3(){
  6. System.out.println("before type advice3 invoked");
  7. }
  8. @Before("execution(* spring.aop.BeanA.m1(..))")
  9. public void beforeAdvice2(){
  10. System.out.println("before type advice2 invoked");
  11. }
  12. @Before("execution(* spring.aop.BeanA.m1(..))")
  13. public void beforeAdvice1(){
  14. System.out.println("before type advice1 invoked");
  15. }
  16. }

执行结果:

  1. before type advice1 invoked
  2. before type advice2 invoked
  3. before type advice3 invoked
  4. m1 invoked

这是由于在切面里面无法通过注解@Order来定义建言的执行顺序,所以这些建言会按照方法名的字典序执行。

弄清楚了以上情况的执行顺序,当以上情况杂糅的时候执行顺序也就清楚了。所谓杂糅是指在某个连接点执行的建言可能有多种不同的类型,同一种类型可能有多个,同一种类型可能来自不同的切面,也可能来自同一个切面,不同类型也可能来自不同的切面。但是不管情况有多复杂,最终都遵循上面列举出来的执行顺序。

3.1.3 构建代理类并生成代理对象

中间插讲了建言的执行顺序,现在回过头来,当获取到与bean相关的所有Advisor之后就可以构建代理类并生成代理对象了,也就是createProxy方法,spring会选择动态代理的方式,代码如下:

  1. @Override
  2. public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
  3. if (!NativeDetector.inNativeImage() &&
  4. (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
  5. Class<?> targetClass = config.getTargetClass();
  6. if (targetClass == null) {
  7. throw new AopConfigException("TargetSource cannot determine target class: " +
  8. "Either an interface or a target is required for proxy creation.");
  9. }
  10. if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
  11. return new JdkDynamicAopProxy(config);
  12. }
  13. return new ObjenesisCglibAopProxy(config);
  14. }
  15. else {
  16. return new JdkDynamicAopProxy(config);
  17. }
  18. }

代码很容易理解,注意一下proxyTargetClass的配置会影响代理方式的选择,另外就是看被代理的类是不是接口类,如果是就用jdk动态代理,不是就用cglib动态代理,这两种代理方式的详解见:代理模式

重点看一下这两种方式是如何生成代理对象的。我们知道对于jdk动态代理来说最终是调用Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)方法来生成代理对象的,这是一个固定的方法,有三个参数, 第二个参数是代理类最终需要实现的所有接口,AOP中代理类要实现哪些接口呢?除了bean原本实现的接口,还至少包括SpringProxy, Advised, DecoratingProxy这三个接口,寻找这些接口的逻辑在JdkDynamicAopProxy类的构造器中。

我们可以将jdk动态代理生成的代理类持久化到文件,然后反编译去查看结果,由于完整的代码比较长,markdown又不支持折叠代码,所以就不贴反编译后的代码了,但是看过之后就会发现代理类没有任何玄机,代理类只是调用了InvocationHandler的invoke方法,所以真正复杂的逻辑在InvocationHandler中,jdk动态代理的InvocationHandler是哪个呢?实际上还是JdkDynamicAopProxy这个类,所以这个类不光负责生成代理对象,还负责执行代理方法。

  1. // JdkDynamicAopProxy实现了InvocationHandler接口,所以也用于执行代理方法
  2. final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
  3. ... // 省略代码
  4. // JdkDynamicAopProxy构造器
  5. public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
  6. Assert.notNull(config, "AdvisedSupport must not be null");
  7. if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
  8. throw new AopConfigException("No advisors and no TargetSource specified");
  9. }
  10. this.advised = config;
  11. // proxiedInterfaces是一个数组,保存了所有代理类需要实现的接口,包括bean原本实现的接口,以及SpringProxy, Advised, DecoratingProxy这三个接口
  12. this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
  13. findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
  14. }
  15. // 生成代理对象
  16. @Override
  17. public Object getProxy(@Nullable ClassLoader classLoader) {
  18. if (logger.isTraceEnabled()) {
  19. logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
  20. }
  21. return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
  22. }
  23. // 执行代理方法
  24. @Override
  25. @Nullable
  26. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  27. ... // 省略代码
  28. }
  29. }

再来看CGlib动态代理的代理类生成过程,cglib动态代理是通过enhance来生成的。cglib动态代理生成回比jdk动态代理更加复杂一些,但也跳不出cglib代理的框架,需要注意一点的是,我们总是有一种先入为主的印象,那就是生成的代理类包含了原有方法的逻辑以及增强的逻辑,其实不是的,无论是cglib动态代理还是cglib生成的代理类是简单的,真正复杂的部分是在拦截器中,对于jdk动态代理来说是InvocationHandler,对于cglib来说是在MethodInterceptor。

3.2 代理方法如何执行

jdk动态代理和cglib动态代理执行代理方法的逻辑是差不多的,jdk动态代理中会将advice封装为MethodInterceptor,多个MethodInterceptor就构成了链,spring按照链的方式执行advice,所谓链就是一个节点执行完了触发下一个节点执行。

四、引入建言

举例

4.1 如何生成代理类

4.2 代理方法如何执行

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注