116

Spring源码解析之AOP篇

 6 years ago
source link: http://www.linkedkeeper.com/detail/blog.action?bid=1048
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Spring源码解析之AOP篇

原 Spring源码解析之AOP篇

张强

作者

张强,16年加入京东,目前就职于京东商城京麦平台组,从事京东对外开放平台和服务于第三方入驻商家的相关工作,开源爱好者,对常用开源框架如Spring、Mybatis、Dubbo等有源码级别的了解,热衷于研究各种技术,致力于成为一名有腔调的工程师

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

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

linkedkeeper0_7d188fca-e33a-4169-ba42-ffc287701a7a.jpg
linkedkeeper0_c13e3972-3f1a-445a-b27a-17c0b20f2c15.jpg
linkedkeeper0_b509a874-9258-4bab-8924-c9a0b267c1c6.jpg

这里提到了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的注册过程:

linkedkeeper0_d31c96cd-9b50-460e-8d1c-cdab881ca8b8.jpg
linkedkeeper0_2c818960-bba1-49e3-9408-08b81d9a936f.jpg
linkedkeeper0_e3d2d8b1-a709-40b6-981e-d5088b81045f.jpg

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

linkedkeeper0_4eaeb516-6456-4aa0-9ed3-f682d6a1874c.jpg

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

linkedkeeper0_b835d45e-6116-49eb-9ed3-bb0e016b116e.jpg

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

linkedkeeper0_52ee3071-66b2-4c4a-9741-ed9da78cdf17.jpg
linkedkeeper0_a7ca554f-2bb8-4b54-abc1-3a6691915eaf.jpg
linkedkeeper0_74f0b791-1525-480e-82cc-2e02c2c31311.jpg
linkedkeeper0_aae1230d-ddea-4493-b391-06997d149926.jpg
linkedkeeper0_7a0969d3-3996-4313-96af-11ebd8818ab8.jpg
linkedkeeper0_79b936d3-5d59-417f-953f-87a80f6af335.jpg
linkedkeeper0_b65a3258-ab15-4132-999e-ec6e33f1a605.jpg

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

linkedkeeper0_679ab87a-d2f6-429c-a48e-b3302f6c25c7.jpg

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

linkedkeeper0_7659774b-ea39-44ef-99c8-8b4a400e8edb.jpg
linkedkeeper0_8a89ab32-3074-43bd-9bd1-26629c3df994.jpg

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

linkedkeeper0_3a87f9ef-b64f-4a38-a43b-97373e9b5e84.jpg
linkedkeeper0_cfc53602-60ef-4001-ba4a-b3eef367fb6d.jpg

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

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

linkedkeeper0_797bda2e-27a2-4e2d-924e-d7ab85a554e5.jpg
linkedkeeper0_28e5f92b-ece0-4c4f-908c-8990723025c6.jpg
linkedkeeper0_38dd7571-e7a9-44f9-aa5d-1658100227b3.jpg
linkedkeeper0_9c02c7b4-04d3-4c22-bb71-535a6c493d75.jpg

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

linkedkeeper0_170a7891-89a6-48a6-9422-75444c49a2fa.jpg
linkedkeeper0_1cc4fdeb-a0ec-4eb5-8423-27f40b4e026e.jpg
linkedkeeper0_d67cc806-3b7d-4047-961b-04d1ce1b71bf.jpg

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

linkedkeeper0_eb4142cc-8028-429b-a8c1-394b3e81d068.jpg
linkedkeeper0_e1b029cd-9f6f-4c74-8724-ba3aae43e631.jpg

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

linkedkeeper0_3b517f80-08be-4f30-af37-28477e6f0bf7.jpg

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

linkedkeeper0_b94e7eda-3175-488c-939d-aaaf893ad9ee.jpg

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

linkedkeeper0_1adeebdf-7a3c-4119-8624-ef4a8b92533c.jpg

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

linkedkeeper0_c0020588-ebd5-4d84-aaed-f6a5d32c2c84.jpg

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

linkedkeeper0_3872a895-bff4-4e20-9c8c-c2ca2afe6ae1.jpg

linkedkeeper0_321886bb-8c36-45f7-92c9-8dff87e077ca.jpg

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

linkedkeeper0_aa7a5be0-011f-4880-bad2-767d7c58270a.jpg
linkedkeeper0_a5cbbfba-59aa-4496-a796-b228e5d45d75.jpg
linkedkeeper0_adf548b2-4a7a-47d6-9ef3-ca650ef9b1c6.jpg
linkedkeeper0_ee2a5874-67b8-4821-853e-1c6617c59b03.jpg

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

linkedkeeper0_ab8efa77-c0fa-488c-b552-d290681c101e.jpg
linkedkeeper0_bff330ae-6cd3-440b-87f0-507dea896223.jpg

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

linkedkeeper0_20cab010-1001-4ddb-9865-ed37a008ea7b.jpg

分析其invoke方法:

linkedkeeper0_29d42e54-acce-498c-98a6-4ba4f5e1bb6f.jpg
linkedkeeper0_bd7516cd-3fd0-4788-92ce-d1a6425c2b73.jpg
linkedkeeper0_5523ef33-e040-48d2-84b5-7e0f572d5978.jpg
linkedkeeper0_01342530-a6a3-406f-82b4-f5ccee08b433.jpg
linkedkeeper0_5ebbdf57-5f17-4e91-b6e0-78b2832c08ef.jpg
linkedkeeper0_e456a03d-cb84-4963-9ddb-f4cd25967ed4.jpg

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

linkedkeeper0_a1c087c4-b165-499e-96c6-c57de608e359.jpg

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

linkedkeeper0_991e3d8e-97bc-49e5-9d49-bcbf7b17d9c9.jpg

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

linkedkeeper0_be00765b-a437-4747-a529-d8af104ca805.jpg

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

linkedkeeper0_8eaea655-1e6e-4bf1-8818-8ffaefe92e74.jpg
linkedkeeper0_687e4d91-ccb0-4e84-85ae-cabbba649f3b.jpg
linkedkeeper0_183ce9f6-7577-4323-83ec-b32194f17e21.jpg

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

linkedkeeper0_5f056d4a-40f6-48f6-9355-e2d8cf5b1feb.jpg

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

linkedkeeper0_22ccd185-6cf6-4696-a9f6-3f1001fad8bf.jpg
linkedkeeper0_d6ae728d-91af-4dff-919c-c02707a6b1ca.jpg
linkedkeeper0_38629750-f1c6-4621-a7fd-3f48b2c21327.jpg

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

linkedkeeper0_0ac132d8-ef67-4f71-9529-cba377030dc4.jpg

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

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

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

linkedkeeper0_8ec00ecc-381d-4c7e-b04d-c2edcf7f849a.jpg

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK