AOP流程源码分析-配置信息解析和代理对象创建
本次文章就算是开始对于aop 的流程源码分析了。区别于IOC,aop 相对来说简单一点,我们后续的主要内容就是从aop 相关配置信息的解析、代理的创建、代理流程的调用,这三个部分着手来简单说一下aop 的源码分析。
简述AOP的前世今生
首先系列文章开头还老套路,先说一下aop 的一些概念知识,其信息来源主还要是网络和官方。对aop 有一定了解的跳过这段内容。
- AOP(Aspect Orient Programming):面向切面编程;
- 用途:用于系统中的横切关注点,比如日志管理,事务管理;
- 实现:利用代理模式,通过代理对象,对被代理的对象增加功能。所以,关键在于AOP 框架自动创建AOP 代理对象,代理模式分为静态代理和动态代理;
- 框架:
AspectJ使用静态代理,编译时增强,在编译期生成代理对象;
SpringAOP使用动态代理,运行时增强,在运行时,动态生成代理对象;
目前 Spring AOP 一共有三种配置方式,Spring 做到了很好地向下兼容,所以可以放心使用。
- Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的
- Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间
- Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做@AspectJ ,但是这个和AspectJ 其实没啥关系。
要说明的是,这里介绍的 Spring AOP 是纯的 Spring 代码,和 AspectJ 没什么关系,但是 Spring 延用了 AspectJ 中的概念,包括使用了AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。如@Aspect、@Pointcut、@Before、@After 等注解都是来自于AspectJ,但是功能的实现还是Spring AOP 自己实现的。
Spring AOP 底层实现机制目前有两种:JDK 动态代理、CGLIB 动态字节码生成。在阅读源码前对这两种机制的使用有个认识,有利于更好的理解源码。
源码分析
源码的话,还是用之前分析IOC 的时候使用的源码,然后创建一个测试类。
1 | public static void main(String[] args) { |
我们今天主要源码分析目的就是测试类中applicationContext 的创建,也就是容器启动时候的内容,后面再说具体方法的调用。
因为之前说IOC 就已经将核心方法refresh 方法全部分析过一次了,对于IOC 没有了解的小伙伴可以从IOC 的第二篇文章:IOC流程解析-BeanFactory的创建 开始看一下。既然看过了,那么我们就直接定位到具体方法。
AOP配置信息解析
首先aop 的标签设置和bean 标签有点不一样,所以我们要看的话,就是先从标签的解析内容开始,即BeanDefinition 的构建。
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
这里就直接定位到代码了,parseBeanDefinitions 方法(这里全局搜索就行,具体怎么找到还是看IOC 的第二篇文章),这里可以看到的是会有两个解析方法的分支,这个我们之前也解释过spring 的schema 机制,所以aop 标签走的应该是第二个方法parseCustomElement 方法。
1 | if (node instanceof Element) { |
跟进方法。
这里的第一个重点就是去获取一个命名空间处理器,而之前介绍schema 机制的时候,就已经说过这个解析器是由对应的标签设置提供的,所以我们要找到aop 对应的“http://www.springframework.org/schema/aop” 标识后,再去根据标识找到对应的spring.handlers 文件。
1 | //解析命名空间,得到一个命名空间处理器 |
要是全局搜索不好找,因为可能有很多配置文件都引入了aop,那么可以直接在xml 中点击标识,跳转到对应的jar 包下面,再去找inf 文件夹下面对应的spring.handlers 文件

这里文件中就存放这aop 标签的解析器。
1 | http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler |
接着直接跟进AopNamespaceHandler 类。
可以看到关于各个标签不同解析器的生成,我们上面配置的是aop 的基础标签aspectj-autoproxy 标签,所以直接找到对应的AspectJAutoProxyBeanDefinitionParser 类。至于这个init 方法是什么时候调用的,我们继续往下看。
1 | public class AopNamespaceHandler extends NamespaceHandlerSupport { |
回到parseCustomElement 方法,跟进其resolve 方法。
我们可以看到init 方法就是这个时机点调用的,也就说这里对创建出不同的解析器对象,然后返回。
1 | NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); |
再次回到parseCustomElement 方法。
既然已经知道了目前的解析器是AspectJAutoProxyBeanDefinitionParser 对象,那么我们就可以继续跟进其parse 方法。
跟进方法。我们重点要看的就是第一个注册AspectJAnnotationAutoProxyCreator 方法。
1 |
|
继续跟进。
这里的目的就是在BeanFactory 中注册AnnotationAwareAspectJAutoProxyCreator 对象。注意这里不是注册到单例池之中,具体是为什么,往下看。
1 | public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( |
我们可以看下AnnotationAwareAspectJAutoProxyCreator 对象的结构。
这个对象的顶层接口是BeanPostProcessor 接口,这说明该对象为一个Bean 的后置处理器对象。所以在BeanFactory 中注册的话,不是注册到单力池中,而是注册到BeanPostProcessor 的集合中。
小结
这里的aop 标签解析就是这样,目的就是往BeanPostProcessor 集合中注册第一个Bean 的后置处理器。
AOP对应的BeanPostProcessor的执行
在上面我们看到已经构建出了AOP 相关的BeanPostProcessor,那么下一步就是要看这些Bean 的后置处理器的执行,所以我们直接定位到Bean 的初始化内容doCreateBean 方法。
定位到该方法后直接找到对应的BeanPostProcessor 执行的相关内容initializeBean 方法的调用。
前置方法解析所有的advisor-postProcessBeforeInitialization方法
跟进该方法。第一个前置方法的调用 ,还是继续跟进applyBeanPostProcessorsBeforeInitialization 方法。不过该方法我们之前已经看过多次了,没什么内容,就是循环出所有的BeanPostProcessor 然后执行对应的postProcessBeforeInitialization 方法。
1 | Object wrappedBean = bean; |
我们直接跟进上面构建的AnnotationAwareAspectJAutoProxyCreator 对象的Before 方法,注意这里跟进去就是其上父类AbstractAutoProxyCreator 对象的实现,因为该对象是没有对Before 方法进行实现的。
跟进代码。找到其重点调用shouldSkip 方法,注意Before 方法是不是创建出代理对象的,但是会加载所有的Advisor。
1 | if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { |
继续跟进。注意这里要跟进后找到其对应的子类重写。然后找到重点方法findCandidateAdvisors方法调用
1 | List<Advisor> candidateAdvisors = findCandidateAdvisors(); |
还是跟进。还是找其对应的子类重写。
这里会首先根据父类规则找到的所有advisor,但是这里是没有的,也就是null,所以要看的是buildAspectJAdvisors 方法的调用。
1 |
|
跟进方法。
首先会获取一次所有的advisors 对象缓存,如果已经有值就直接返回,如果没有值才会进行解析,目的就是为了只解析一次,因为所有的bean 初始化都会走这个BeanPostProcessor,所以要避免重复解析。
1 | List<String> aspectNames = this.aspectBeanNames; |
然后是获取所有的beanName,循环遍历。
1 | // 1.1 获取所有的beanName |
第一个重点来了,一定是要被Aspect 注解修改的接口,才会被构建成advisor 对象。
1 | // 1.4 如果beanType存在Aspect注解则进行处理 |
第二个重点,调用getAdvisors 方法获取由当前对象的所有方法封装而来的Advisor 对象集合。
1 | // 使用BeanFactory和beanName创建一个BeanFactoryAspectInstanceFactory,主要用来创建切面对象实例 |
跟进getAdvisors 方法,看下具体封装内容。
这里是根据方法上修饰的不同注解,来封装成不同的Advisor 对象。
1 | // 2.获取Aspect()标注的类名 |
继续跟进就没有必要了,我们看下大致的写法。
首先是@Aspect 注解修饰类,说明这个类是切面类,可以被aop 找到
然后是@Pointcut 注解修改第一个方法,所有需要被拦截的具体对象
最后就是@Before 注解修改前置方法、@After 注解修饰后置方法、@Around 注解修改环绕方法。
1 |
|
后置方法生成代理对象-postProcessAfterInitialization方法
还是回到AbstractAutoProxyCreator 对象,直接找到postProcessAfterInitialization 方法。
跟进代码。
首先是生成用于缓存的key,然后调用wrapIfNecessary 方法。
1 | //2.如果beanName为空,使用Class对象作为缓存的key |
继续跟进。
这里先获取到上面解析缓存的所有Advisors,我们可以先看下getAdvicesAndAdvisorsForBean 方法。
1 | // 4.获取当前bean的Advices和Advisors===》 |
跟进后直接就是重点调用findCandidateAdvisors 方法,这里我们就不看了,直接再次跟进。
这部分可以看到,第一步是获取所有的Advisor 对象,然后筛选出拦截本对象的Advisor。注意我们目前还是在Bean 对象的初始化过程中,那么这里指的对象就是正在初始化的对象。最后再将符合条件的Advisor 对象排个序即可,这里的排序是为了将环绕、前置、后置方法进行排序,方便后面代理对象执行时的调用顺序。
至于findCandidateAdvisors 方法,这里跟上面的调用时一模一样的,唯一的不同就是这次不用再次解析了,而是直接返回上面的缓存集合即可。
1 | protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { |
回到wrapIfNecessary 方法。
继续就是创建代理的部分了,最后会将创建好的代理存入缓存,并且返回结果,这个方法后面我们就不再回来了,要看好。
1 | // 5.如果存在增强器则创建代理 |
继续跟进createProxy 方法。
这里的代码多,可以都不用管,就看后面的最后一句。
1 | proxyFactory.getProxy(getProxyClassLoader()); |
跟进代码。
不要急着看getProxy,先看createAopProxy 方法。
1 | public Object getProxy( { ClassLoader classLoader) |
这里的调用比较简单,我就不一步步跟了,直接看结果。
这里会判断出使用的到底是Cglib 代理还是JDK 代理。直接说结论就是默认使用JDK 代理,配置了使用Cglib 代理。JDK 代理对应对象:JdkDynamicAopProxy,Cglib 代理对应对象:ObjenesisCglibAopProxy。这里一定要记住这个两个代理对应的对象,后面我们走流程的时候,看代理直接看对应对象的invoke 方法。
1 |
|
再回看getProxy 方法就简单了,因为我们没有配置,所以这里就是jdk 代理的使用了。
就是通过classLoader、接口、InvocationHandler实现类,来获取到代理对象。
1 |
|
小结
这样生成代理的内容就结束了,其主要就是看到底是用cglib 还是jdk 来做代理,还有就是advisors 的解析。
总结
本次就是了解aop 的一些概念,当然网上找的肯定比我全,因为我也是找的,这个不是重点,主要还是aop 的代理创建和执行时机的描述,后面我们再分析请求的完整流程。
附录Spring 源码分析系列文章
IOC
时间 | 文章 |
---|---|
2022-03-09 | Spring的基本概念和IOC流程的简述 |
2022-03-11 | IOC流程解析-BeanFactory的创建 |
2022-03-14 | IOC流程解析-BeanFactoyPostProcessor和BeanPostProcessor |
2022-03-15 | IOC流程解析-实例化和初始化 |
2022-03-17 | IOC流程解析-循环依赖 |
AOP
时间 | 文章 |
---|---|
2022-03-19 | AOP流程源码分析-配置信息解析和代理对象创建 |
2022-03-20 | AOP流程源码分析-请求调用全流程 |