Spring源码解析之AOP篇
您目前处于:编程  2017年11月20日

Spring AOP是我们日常开发中经常使用的工具,常被用来做统一的日志、异常处理、监控等功能,使用方法在此不多赘述,有兴趣的读者可以自行去网上查阅资料进行学习,我们以注解的使用方式为例,分析其相关源码,其他方式大同小异。

开启Spring AOP注解方式首先要配置<aop:aspectj-autoproxy/>标签,我们就以这个标签的解析作为入口来分析,这里需要读者对Spring自定义标签解析的过程有一定的了解,笔者后续也会出相关的文章。锁定AopNamespaceHandler:

这里提到了proxy-target-class和expose-proxy两个属性,简单介绍一下,Spring提供了JDK动态代理和CGLIB代理两种方式为目标类创建代理,默认情况下,如果目标类实现了一个以上的用户自定义的接口或者目标类本身就是接口,就会使用JDK动态代理,如果目标类本身不是接口并且没有实现任何接口,就会使用CGLIB代理,如果想强制使用CGLIB代理,则可以将proxy-target-class设置true,这两种代理方式在使用的时候有一些需要注意的事项,JDK动态代理是基于实现目标类的接口来创建代理类的,所以只有接口方法会被代理,其他方法不会被代理,而CGLIB代理是基于继承目标类实现的,所以不能被继承的方法(例如final修饰的方法、private修饰的方法等)是不能被代理的,建议尽量使用JDK动态代理的方式创建代理类。expose-proxy用来解决对象内部this调用无法被切面增强的问题,例如我们在A类的对象内部x方法中调用另外一个内部方法y时,y方法不会被切面增强,这时可以配置expose-proxy为true并将this.y()改为((A)AopContext.currentProxy()).y(),即可让y方法被切面增强。下面让我们来看本篇文章的主角AnnotationAwareAspectJAutoProxyCreator的注册过程:

我们发现优先级的判断就是根据类在APC_PRIORITY_LIST中的索引值来判断的,索引值越小的优先级越高,我们看一下APC_PRIORITY_LIST的内容:

我们发现它是一个ArrayList,并且在静态块中为其add了三个类,也就是这三个类的优先级依次降低。注册完AnnotationAwareAspectJAutoProxyCreator之后,要怎么使用这个bean呢,我们看一下它的层次结构:

我们发现这个类间接实现了BeanPostProcessor接口,我们知道,Spring会保证所有bean在实例化的时候都会调用其postProcessAfterInitialization方法,我们可以使用这个方法包装和改变bean,而真正实现这个方法是在其父类AbstractAutoProxyCreator类中:

上面这个方法相信大家已经看出了它的目的,先找出所有对应Advisor的类的beanName,再通过beanFactory.getBean方法获取这些bean并返回,这里就是通过父类获取其他aop配置信息。下面我们来看注解aop配置信息的获取:

方法很长,不过逻辑很清晰,首先获取所有bean,然后过滤掉不满足子标签配置过滤条件的bean,接着判断bean是否有@Aspect注解,最后解析注解的配置内容并放入缓存中,我们分部来看:

这里的includePatterns就是文章开始解析<aop:aspectj-autoproxy/>子标签<aop:include/>的配置时织入的,有兴趣的读者可以了解一下具体用法,这里不多赘述。

这里提到了aspect的初始化模式,目前一共有6种,对应PerClauseKind这个枚举,这里不做详细说明,大家可以到aspect官方文档进行了解,这里给出地址:


https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/index.html

这里我们看到aop相关的一些注解的提取,下面就是初始化过程了:

到这里整个aop注解方式的初始化工作就完成了,不知道大家是否还记得我们是怎么一步一步的走到这里的,我获取到了所有的候选增强器,下面要匹配适用于当前bean的增强器:

上面的方法中提到引介增强的概念,在此做简要说明,引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,我们可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建实现该接口的代理,使用方法可以参考文末的引用链接。另外这个方法用两个重载的canApply方法为目标类寻找匹配的增强器,其中第一个canApply方法会调用第二个canApply方法并将第三个参数传为false:

我们来看一下前面初始化的InstantiationModelAwarePointcutAdvisorImpl的层次结构:

我们看到它实现了PointcutAdvisor接口,所以会调用红框中的canApply方法进行判断,第一个参数pca.getPointcut()也就是调用InstantiationModelAwarePointcutAdvisorImpl的getPointcut方法,这个方法的返回值就是我们看到的在InstantiationModelAwarePointcutAdvisorImpl初始化时传入的AspectJExpressionPointcut,我们以AspectJExpressionPointcut作为第一个参数继续跟踪canApply方法:

我们跟踪pc.getMethodMatcher()方法也就是AspectJExpressionPointcut的getMethodMatcher方法:

发现方法直接返回this,也就是下面methodMatcher.matches方法就是调用AspectJExpressionPointcut的matches方法:

getShadowMatch方法里面就是调用aspect提供的api来判断当前类是否满足execution表达式的规则,有兴趣的读者可以查阅aspect的相关资料进行学习。在获取了所有bean匹配的增强器之后,就可以创建代理了:

这里我们看到了Spring如果选择使用JDK动态代理还是CGLIB代理,optimize用来控制通过CGLIB创建的代理是否使用激进的优化策略,这个配置对JDK动态代理无效,不推荐使用,proxy-target-class文章开始已经介绍过,逻辑就是在这里实现的,hasNoUserSuppliedProxyInterfaces判断目标类是否没有用户自定义的代理接口。我们先看JDK动态代理的方式:

这里我们看到为即将创建的代理类添加了3个接口,后面会用到。JDK动态代理还有一个关键的角色就是InvocationHandler,这里传入的this,所以我们断定,JdkDynamicAopProxy一定实现了InvocationHandler接口:

分析其invoke方法:

在DefaultAdvisorAdapterRegistry初始化时初始化了3个适配器,这里我们以MethodBeforeAdviceAdapter为例,也就是对应@Before注解创建的advice的适配器:

下面我们来看拦截器链的调用:

这里我们以刚刚适配的MethodBeforeAdviceInterceptor为例:

这里首先执行拦截器的before方法,然后再次执行上面的proceed方法进行下一个拦截器方法的调用,这里的advice也就是获取候选增强器时生成的AspectJMethodBeforeAdvice:

这里的aspectJAdviceMethod也就是我们应用程序中@Before注解的方法了。我们再来看一个@After注解对应的advice是如果执行的,锁定AspectJAfterAdvice:

我们发现是在finally块中执行了拦截器方法,也就是@After注解的方法会在目标方法执行之后执行。下面我们来看一下CGLIB代理的方式,这里需要读者去了解一下CGLIB以及其创建代理的方式:

这里将拦截器链封装到了DynamicAdvisedInterceptor中,并加入了Callback,DynamicAdvisedInterceptor实现了CGLIB的MethodInterceptor,所以其核心逻辑在intercept方法中:

这里我们看到了与JDK动态代理同样的获取拦截器链的过程,并且CglibMethodInvokcation继承了我们在JDK动态代理看到的ReflectiveMethodInvocation,但是并没有重写其proceed方法,只是重写了执行目标方法的逻辑,所以整体上是大同小异的。

到这里,整个Spring 动态AOP的源码就分析完了,Spring还支持静态AOP,这里就不过多赘述了,有兴趣的读者可以查阅相关资料来学习。


本文受原创保护,未经作者授权,禁止转载。 linkedkeeper.com (文/张强)

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!

赞赏支持
    分享到: 更多