Sping AOP 代码怎么读

星矢  |  2017. 10. 09   |  阅读 1396 次

Spring AOP的代码怎么读

Spring的体系越来越庞杂,但还是有童靴因为各种原因想翻开代码去看看其中的具体实现。写个短文,给想看代码的童靴一点参考。仅仅涉及Spring Framework里面的AOP部分。并讨论几个难点实现上的细节。
因为代码量特别大,如果盲目从类包开始一个一个走查效率很低,而且容易迷失在细节里面。如果目标是快速理解Spring内部实现原理,并找到难点的实现方式,那还是有些技巧可以事半功倍的。那么代码怎么看才能快速了解原理呢?

第一步不是直接开撸,上来就看代码,而是了解实现了什么功能?

平时spring虽然平时用的多,但是根据80/20原则,我们平时都是用最常见的部分功能,但是很多功能还没有使用过。而这么庞大的代码量都是针对所有功来设计来实现的,先熟悉实现了什么功能,最好的是看说明文档,如果只是关注Framework本身的核心功能,只看IOC和AOP章即可。 先看AspectJ的aop的核心概念,用法和api。 至少要熟读以下几个章节:

  • 9.1节讲述了最基本的核心概念。
  • 9.2讲述了基于@AspectJ情况下spring AOP的最常见的使用方法。
  • 9.6讲述了基本的实现原理。
  • 10节讲述了spring 的AOP api,虽然是为了兼容较早版本的spring aop使用方法,但是最新的spring框架中实现aop功能还是使用了同一套api,对他们有个初步的了解十分必要。

补一下基础知识:

  • Spring ioc整个初始化过程:
    • bean的定义(BeanDefinition)的加载过程
    • bean的实例的生成过程
  • spring IOC的扩展点
  • cglib 的基本使用方法,enhancer的常用api
  • jdk动态代理的使用方法

写个例子,调试代码,找到实现入口。

为了详细了解aop的调用过程,我们最好写一个最简单的例子,麻雀虽小五脏俱全。边调试边分析

@Component
public class FooService {
    public void sayHello() {
        System.out.println("hello world");
    }
 }  

@Aspect
@Component
public class AspectExample {
   @Before("execution(public * *say*(..))")
   public void doBefore() {
       System.out.println("before say method!");
   }
}

public class TestSpring {

   @Configuration
   @EnableAspectJAutoProxy
   @ComponentScan("test.spring")
   public static class Config {
   }

public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
    FooService fooService = (FooService) context.getBean("fooService");
    fooService.sayHello();
}

}

例子很简单:实现了拦截FooService中的sayHello方法,我们使用AspectExample的dobefore来拦截,在之前打印字符串"before say method"。可以直接运行查看效果(要把类放置到test.spring包下)

看代码要找到入口,那么AOP处理的入口在哪里呢? 我们知道aop是对spring框架的扩展,是使用BeanPostProcessor, 入口也很容易找到,BeanPostProcessor能对应用的实例做手脚的地方就是在接口BeanPostProcessor的方法postProcessAfterInitialization中实现的, 是在AbstractAutoProxyCreator类的postProcessAfterInitialization方法,(具体到我们的case是它的子类,AnnotationAwareAspectJAutoProxyCreator)。 spring设计的高明之处就是松耦合的为框架添加特性,使用ioc自带的扩展点来实现了AOP的扩展功能。

有了大的入口,带着核心的问题去调试研究。

如果说你看完doc文档后,那问问题的基本原则就是是如果是这样一个功能自己来实现的话,有什么难点,有什么不是拿不定注意怎么设计的? AOP部分,文档中已经给出实现方式是动态代理,那么一个基本的问题就是ioc过程中是什么时机判定一个bean是否需要生成代理呢?在bean的初始化过程中来生成代理类,有可能advice类还没有初始化,怎么找到所有的adivce,并把他拦截到方法的调用点上,确保方法调用的时候相应的advice能够执行?
第一个问题是时机问题. 第二个问题涉及到两个阶段,一个阶段是代理类的生成,一个阶段调用的时候确保命中的advice能按顺序执行。

1.判断一个类是否需要生成代理,拼装生成代理需要的参数。

拿我们一开始的疑问点,怎么发现一个bean是否需要生成代理。那么必须把所有的advice遍历,把pointcut注解解析,根据pointcut的定义是否命中当前的bean来判定。这个还要和advice是否实例化无关系,因为advice也是普通的bean,未必已经完成初始化。 针对这个部分,参考时序图一。 可以看到,最早的入口在AbstractAutoProxyCreator.postProcessAfterInitialization(),其中调用了父类,AbstractAutoProxyCreator.wrapIfNecessary(),getAdvicesAndAdvisorsForBean(),findEligibleAdvisors() 最后调用了AnnotationAwareAspectJAutoProxyCreator=>BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors()=>之中会通过BeanFactoryUtils.beanNamesForTypeIncludingAncestors来得到所有的bean, 该方法遍历所有的beandefination来得到所有的bean,我们知道bean初始化之前所有的bean的定义已经加载好,和advice的bean的实例化无关。 spring框架中使用advisor类来封装advice和pointcut. AOPUtil的canApply方法来判断advisor是否match对应的bean,如过每个pointcut有match方法来判断是否命中当前的bean,如果命中那么该bean需要创造代理。
这部分核心还是在AbstractAutoProxyCreator,他处理了判断bean是否需要创建代理的总体流程。他是AnnotationAwareAspectJAutoProxyCreator的父类的父类。AnnotationAwareAspectJAutoProxyCreator和它的父类AspectJAwareAdvisorAutoProxyCreator主要是增加了处理@Aspect的逻辑,通过这个注解来找到所有的advice和pointcut定义并进行解析生成advisor。 图一

2.创建代理类

知道某个bean需要创建代理后,后边的核心步骤就是怎么创建代理类。继续阅读wapIfNecessary方法,调用了内部的createProxy方法,而createProxy方法使用的ProxyFactory来创建代理了,这个ProxyFactory在文档中有相应的介绍,本身也是开放给用户来用使用代码创建代理的方式。看来好好读手册还是很有必要。 https://docs.spring.io/spring/docs/4.1.1.RELEASE/spring-framework-reference/htmlsingle/#classic-aop-prog proxyFactory的基本用法就是把要代理的bean和相关的advisor设置到proxyFactory,然后通过proxyFactory.getProxy(getProxyClassLoader())方法来生成代理类。参考文档中的例子:

ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addInterceptor(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

本质上之前的所有过程就是为proxyFactory拼装参数的过程。内部怎么生成代理类还是需要研读proxyFactory的代码,内部是使用了DefaultAopProxyFactory来创建代理类的,DefaultAopProxyFactory判断当前类是否是有接口,如果没有借口那么使用cglib来生成代理类,这里是ObjenesisCglibAopProxy也就是CglibAopProxy的子类在spring4.0中引入,尝试不调用构造函数就生成类的实例。

之后的核心逻辑在CglibAopProxy类的getProxy()方法来生成代理类,这里需要一些cglib的用法基础知识,cglib使用Enhancer类来生成代理,Enhancer可以设定使用callbacks和callbackFilter,来使得不同的方法走不同的callback,可以参考cglib的API文档。没有这些基础知识可能看不大懂。spring为了性能更好些和一些特殊的方法比如hashcode方法和equals方法,会生成6种不同的callback。这里的callback就是对于方法调用的时候你可以拦截加入自己逻辑的地方,对于对于需要advice拦截的方法是使用DynamicAdvisedInterceptor这个callback来处理的。JdkDynamicAopProxy的实现分析从略,总体上和cglib功能类似,使用的创建代理的api不同。代理类虽然生成了,我们还没分析处理切面的逻辑在哪里。 图二

3.代理类的方法被调用时候发生了什么 。

具体到我们的case代理类的sayHello方法被调用发生了什么?如果从实现的通用角度来讲是需要考虑一个bean有多个方法,有的方法需要切面,有的方法不需要,根据advisor的pointcut是否命中。 得到切面的方法可能有多个advice需要调用,(spring还支持为每个advice有order,这样可以控制先后的拦截顺序)。怎么处理这个问题呢? 抽象成核心问题就是:1.拦截链怎么生成。2.调用的时候怎么实现链式调用。这些可以再DynamicAdvisedInterceptor的intercept方法找到答案, 拦截链是一个MethodInterceptor这个接口的list。MethodInterceptor这个接口是aopalliance中定义的,也就是aop联盟中使用方法拦截器这个概念.Advisor是Spring框架中的类,也包含了拦截的概念。他们之间是有响应的转换关系的,DefaultAdvisorAdapterRegistry的public MethodInterceptor[] getInterceptors(Advisor advisor) 方法。 intercept方法通过this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)来得到所有的MethodInterceptor。第一个问题因为就是方法级别的拦截链每次方法调用的时候都需要调用,所以性能也是需要考虑的因素。内部对每个方法生成的调用链都使用了cache来存储,只有第一次需要生成,后续直接复用即可。

怎么实现链式调用的,这部分有点绕,是使用 CglibMethodInvocation extends ReflectiveMethodInvocation 的proceed方法和MethodInterceptor的invoke方法互相配合完成的。CglibMethodInvocation的proceed会调用Interceptor的invoke方法,MethodInterceptor在合适的位置调用完advice之后会再调用CglibMethodInvocation的proceed方法,此时Proceed已经完成了一个Interceptor的处理,继续处理下一个MethodInterceptor。循环往复直到所有的MethodInterceptor都处理完成,之后就开始真实调用tagetclass的真实方法。 我们这个例子里面用到的是,MethodBeforeAdviceInterceptor,其他还有ThrowsAdviceInterceptor和AfterReturningAdviceInterceptor等。 可以参考MethodBeforeAdviceInterceptor的实现最后调用了mi.proceed()

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {
    //....省略代码

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
        return mi.proceed();
    }
}

这部分的核心在DynamicAdvisedInterceptor。注意下DynamicAdvisedInterceptor内部的AdvisedSupport成员变量是如何传递进来的,是在第二部分创建代理类的时候注入的。读者可以自行查看。 图三

大块的步骤和逻辑拆分后,要读懂代码还要面对具体很多具体实现时候各种情况的考虑和特殊情况的处理。

这部分是在平时阅读代码的时候经常看到的,比如:

  • 我们的advice有一个特殊类型,introduction。就是让一个对象扩展实现某个特殊的接口,这个和其他几种advice都不一样是围绕着方法的执行前后来切面的。
  • 还有就是pointcut表达式可以写的比较复杂,包含control flow表达式,那么实际上需要生成一个特殊的MethodIntercenptor来处理。
  • 针对pointcut本身也区分静态和动态的区分,如果pointcut表达式内只用到了类名和方法名那么就是静态的,不需要再每次方法调用的时候都重新求值,看看是否命中。如果表达式里面涉及到了调用的方法的参数值,那么就是动态的需要在每次方法调用的时候取求值才知道是否命中。
  • 还有一些兼容IOC里面bean的初始化过程处理循环依赖以及bean的scope是区分单例还是prototype都有一些相应处理。

这些都要先熟悉手册和功能,知道类背景知识才知道代码是写来何用的,这里是细节的黑暗深林,里面有很多坑需要啃。 最后,祝大家代码阅读的愉快。

分享到

   
从 Template 到 DOM(Vue.js 源码角度看内部运行机制)