
Java核心知识体系4:AOP原理和切面应用
1 概述
我们所说的Aop(即面向切面编程),即面向接口,也面向方法,在基于IOC的基础上实现。
Aop最大的特点是对指定的方法进行拦截并增强,这种增强的方式不需要业务代码进行调整,无需侵入到业务代码中,使业务与非业务处理逻辑分离。
以Spring举例,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。
aop的实现主要包括了两个部分:
- 匹配符合条件的方法(Pointcut)
- 对匹配的方法增强(JDK代理、cglib代理)
spring针对xml配置和配置自动代理的Advisor有很大的处理差别,在IOC中主要是基于XML配置分析的,在AOP的源码解读中,则主要从自动代理的方式解析,分析完注解的方式,再分析基于xml的方式。
2 案例分析
下面是spring aop的用法 也是用于源码分析的案例
切面类:TracesRecordAdvisor
xml配置: aop的注解启用只需要在xml中配置这段代码即可,这个是作为入口
服务类:PayServiceImpl 使用jdk代理 所以要有一个接口
测试方法:
执行结果:
从上面的执行结果看,payMoneyMenthod 方法的确是被增强了。
3 BeanFactoryPostProcessor
读spring源码的时候,可以首先看下BeanFactoryPostProcessor和BeanPostProcess,这两个接口都是在spring通过配置文件或者xml获取bean声明,生成BeanDefinition后,允许我们再对生成的BeanDefinition,进行入口包装和增强。
我们看看BeanFactoryPostProcessor的定义
方法postProcessBeanFactory的参数为ConfigurableListableBeanFactory,我们之前讨论过beanFactory用来获取bean的,而ConfigurableListableBeanFactory继承接口SingletonBeanRegistry和BeanFactroy,所以可以访问到已经生成过的BeanDefinitions集合,如果某个类实现该接口,spring会注册这个类,然后执行这个类的postProcessBeanFactory方法,以便我们对BeanDefinition进行扩展。
接下来的代码表示Spring是如何注册BeanFactoryPostProcessor并执行postProcessBeanFactory的。
核心方法1obtainFreshBeanFactory就是前两篇所说的生成BeanDefinition的入口,invokeBeanFactoryPostProcessors核心方法2就是执行BeanFactoryPostProcessor接口的方法。
通过方法getBeanFactoryPostProcessors获取注册BeanFactoryPostProcessor,然后来看看如何添加一个处理器
对于方法invokeBeanFactoryPostProcessors不再往下看了,里面的方法大致先对BeanFactoryPostProcessor进行排序,排序的标准是是否实现了PriorityOrdered,然后根据设置的order大小指定执行顺序,生成一个排序集合和一个普通的集合,最后执行invokeBeanFactoryPostProcessors
这个方法就会循环先前注册的BeanFactoryPostProcessor集合,然后执行postProcessBeanFactory。
4 BeanPostProcess 解读
与BeanFactoryPostProcessor相比,BeanPostProcess就重要得多了,因为Spring的注解、AOP等都是通过这个接口的方法拦截执行的,它贯穿了Bean创建过程的整个生命周期,在IOC阶段,Spring只注册BeanPostProcess,执行则放到了Bean的实例化创建阶段。
首先看下BeanPostProcessor的接口定义
在bean的声明周期中,下面的序列是bean创建后要执行的接口和方法顺序:
- 实例化(autowireConstructor或者instantiateBean)
- 属性初始化(populateBean)
- Aware接口(如果你的 bean 有进行实现)
- BeanPostProcess.postProcessBeforeInitialization
- PostConstructInitializingBean.afterPropertiesSet
- BeanPostProcess.postProcessAfterInitialization
其中通过注解引入依赖的方式就是在AutowiredAnnotationBeanPostProcessor这个类中实现的,而接下来要分析的Spring Aop也是从这里开始的,这个类叫AnnotationAwareAspectJAutoProxyCreator,
5 NameSpaceHanlder 解读
在Spring中,任何的技术都是在IOC的基础上进行的,Aop也不例外,程序会首先读取xml配置文件,然后对读取到的标签先查找命名空间,然后找对应的NameSpaceHandler,最终调用parse方法解析标签。
aop标签的解析,使用纯注解的方式aop:aspectj-autoproxy和使用aop:config的配置解析不太一样,具体表现在生成PointCut和生成Before、After、Around等切面类时,使用aop:config的方式会为这些注解生成一个BeanDefinition,而这个BeanDefinition的构造函数是由3个BeanDefinition组成,表明这个类是合成类,即synthetic这个属性为true。然后跟解析普通的bean一样,生成这些实例对象,后面的过程就跟是用纯注解的方式相同了,接下来的分析是基于纯注解分析的,也就是解析从解析aop:aspectj-autoproxy这个标签开始。
前面的xml文件的标签解析是通过parseDefaultElement方法解析默认的标签的,而我们在配置文件里面配置了启动自动代理的方式<aop:aspectj-autoproxy/>
,当Spring读取到这个标签,则会走parseCustomElement(root)这个方法了,这个方法的源码不再解析,主要完成的功能如下:
- 获取element的nameSpaceUri,根据nameSpaceUri找到NameSpaceHanlder
- 调用NameSpaceHanlder的parse方法解析element
下面是NameSpaceHanlder接口的定义
这里面的init方法是我们初始化操作的,这里可以完成对指定的标签设置解析器,然后再parse方法里面找到指定标签的解析器,然后调用该解析器的parse方法解析标签,后面会重点看这两个方法。
再来看下Spring如何加载NameSpaceHanlder的,Spring首先会取查找项目空间下目录META-INF/的所有spring.handlers文件,这个文件是在Spring依赖的jar下面,在核心jar包都会由这个文件,aop的jar包路径下文件内容为:spring.handlers
发现这里面存储的是一个key,value,key是aop的nameSpaceUri,value是AopNamespaceHandler,从这个类名上就能发现该类实现了NamespaceHandler,肯定也就实现了init和parse方法,所以解析<aop:aspectj-autoproxy/>
的任务就由AopNamespaceHandler的parse完成。
查看AopNamespaceHandler的init方法
上面的代码就很清晰了,<aop:config>
标签由ConfigBeanDefinitionParser处理,<aop:aspectj-autoproxy/>
则由AspectJAutoProxyBeanDefinitionParser这个类处理,这两种处理其实对应了自动代理和通过xml配置的处理方式,然后会调用AspectJAutoProxyBeanDefinitionParser的parse方法
这个方法其实就是为了注册一个AnnotationAwareAspectJAutoProxyCreator类,然后AOP的所有处理逻辑都会交给这个类处理,由于这个类的实现了BeanPostProcessor,所以这个类的入口就是BeanPostProcessor接口的两个方法:
- postProcessBeforeInitialization
- postProcessAfterInitialization
6 Spring Aop 源码分析
我们前面分析了,当spring读取xml文件遇到<aop:aspectj-autoproxy/>
会找到AopNamespaceHandler这个处理类,然后这个类又将这个标签委托给了AspectJAutoProxyBeanDefinitionParser类,最终调用这个类得parse方法,parse方法未做分析,其实这个方法的目的很简单,就是注册AnnotationAwareAspectJAutoProxyCreator这个类,这个类实现了BeanPostProcessor和InstantiationAwareBeanPostProcessor接口,最终在实例化bean对象也就是执行BeanFactory.getBean(beanName)的过程中,会调用这两个接口的方法(执行顺序如下):
InstantiationAwareBeanPostProcessor先执行:
BeanPostProcessor再执行:
AOP的实现基本上是在这两个方法中进行的,所以就从这里来看Spring是如何实现AOP的,Spring的AOP代理目前支持方法的增强,看源码目前好像也支持了属性的增强了。
读取源码前首先来分析一下方法增强的原理,有助于我们读取源码时紧紧抓住主线。首先第一个问题,如果我们想对一个类的方法进行增强,我们应该怎么做呢?
这种业务需求可以通过代理实现,在方法执行前,拦截这个方法,并且加入要执行增强的逻辑,最后再执行目标方法。下面是Spring用的两种代理方式:
JDK代理:我们可以通Proxy类获取一个目标类的代理对象,但JDK代理要求被代理的类必须实现接口,所以是基于接口的代理。
cglib代理:如果目标类没有接口,使用cglib代理,是由asm封装的,直接操作类得字节码,效率也很高。
由于在生产业务中,我们不可能对所有的类都执行增强,所以还需要一个选择器,将符合条件的bean进行增强,Spring使用了PointCut接口,通过该接口的getMethodMatcher方法获取一个方法匹配器,然后通过matches方法匹配到目标类对象的目标方法执行增强操作。mathcer匹配规则就是通过Spring 配置的expression表达式了。
所以在分析源码的时,要围绕这两方面进行:
- 匹配切点方法(构建切入点表达式类和切面类)
- 创建代理对象
这两方面在Spring的实现里非常复杂,尤其是第一步匹配切点方法过程,这个过程中,Spring会将@Aspect注解类的@Before,@After,@Around、@Pointcut等注解都封装成待执行的切面方法类,然后通过方法匹配器匹配到的要增强的方法前后执行切面方法类,达到方法增强的目的。
第二阶段,创建代理对象默认是通过JDK代理实现配置,<aop:aspectj-autoproxy proxy-target-class="true">这样配置可以指定使用cglib代理。
7 注解切面代理类分析
上面分析了真正实现AOP功能的是AnnotationAwareAspectJAutoProxyCreator,由于这个类实现了BeanPostProcessor和InstantiationAwareBeanPostProcessor,所以在创建一个bean的时候,会进入到这两个接口的方法,这两个接口包含了四个方法,方法执行顺序上面已经分析过了,来看看这个类的类图:
类图上比较重要的接口就是右上角实现的两个接口,在bean创建的生命周期过程中,会校验当前容器中是否注册了实现了这两个接口的类,如果有则调用接口的方法,前面的分析中在解析aop:aspectj-autoproxy/时,将这个类注册到了容器中,而且上面也罗列了这两个接口中四个方法的调用顺序,在这个类中完成主要功能的2个方法及其执行顺序:
- InstantiationAwareBeanPostProcessor先执行:
- BeanPostProcessor再执行:
postProcessBeforeInstantiation方法主要是找出注解了Advice的类,并将Advice的类使用了@Before,@After,@Around、@Pointcut,@AfterThrowing等注解的方法封装成一个一个类放入到缓存中供匹配到的类生成代理用。postProcessAfterInitialization主要是匹配符合条件的目标类对象,然后生成代理的过程,接下来就按顺序分析这两个方法完成的功能。
8 Aspect注解类分析
这个方法主要是先为beanClass生成一个缓存的key,这个beanClass如果是FactoryBean,则按照工厂类的命名规则命名,否则用beanName命名,然后用刚才生成的key判断beanClass是否已经存在于Advice的缓存集合中,如果已经存在则代表该类是切面类而且已经被处理过了,后续处理不会为该类生成代理,如果没有没处理过,则会调用下面的方法校验该类是否是AOP的基础类 ,总之这个方法作用就是将AOP相关操作的切面类和基础类放入到缓存中,当为bean生成代理的时候,忽略advice缓存中的AOP切面类和基础类,下面是具体校验过程:
AnnotationAwareAspectJAutoProxyCreator重写了该方法
父类的isInfrastructureClass方法
里面isAssignableFrom表示当前类是否允许被设置为beanClass类对象,可以以此判断beanClass是否是Advice类,所以这个方法的校验目的就是判断当前正在创建目标类是否是AOP的基础类,即该类是否是Advice,Advisor或者实现了AopInfrastructureBean接口。该方法调用父类的isInfrastructureClass判断是否是aop基础类,然后再校验当前类是否使用@Aspect注解,目的只有一个,如果是Advice切面相关的类不做任何处理,直接放入advice缓存即可。
然后再来看shouldSkip(beanClass, beanName):
这个方法主要是校验当前正在创建bean的beanName是否属于已经创建好的切面类缓存中,如果是则加入到advices缓存中,不再处理。其中findCandidateAdvisors()会查找当前容器中生成的所有实现了Advisor的类,Spring会将@Before,@After,@Around等生成一个继承了Advisor类对象存储到缓存中供后续使用,这一部分时Spring AOP前半段的核心内容,后续都会围绕着如何将切面类的注解生成Adisor类探索。
AnnotationAwareAspectJAutoProxyCreator重写了findCandidateAdvisors方法,所以会执行到该方法:
这个方法会首先调用父类的findCandidateAdvisors方法用于获取通过xml文件配置生成的Advisor,也就是通过aop:before,aop:after等生成的,然后调用通过注解方式即@Before,@After,@Around、@Pointcut,@AfterThrowing生成的advisor,可以说,这两个方法分别处理了基于xml配置文件的方式和基于注解的配置方式,因为所有的分析都是基于AnnotationAwareAspectJAutoProxyCreator这个类进行的,所以在这个地方会先获取配置文件的,再生成基于注解类的Advisor,这样就将基于xml配置的和基于注解的配置都会解析到。
看下 this.aspectJAdvisorsBuilder.buildAspectJAdvisors()
这个方法主要的任务其实就是获取类得类型为Aspect的切面类,然后获取切面类方法的所有注解并将注解转换成Advisor类返回,主要步骤为:
- 获取容器中所有的BeanDefinition的beanName
- 根据beanName,或者beanClass,匹配符合规则的Aspect切面类,通过aop:include配置的规则
- 获取Aspect切面类的所有切面方法封装成Advisor对象返回。
- 将获取到的所有Advisor放入到缓存中。
这个方法代码虽然很多,但是核心的是this.advisorFactory.getAdvisors(factory),即第三个步骤,这个方法将会获取到切面类的所有切面方法,并封装成Advisor,getAdvisors是一个接口,ReflectiveAspectJAdvisorFactory实现了这个接口,下面代码是其实现逻辑:
这个方法首先已经将切面类信息封装到AspectMetadata的类再次封装到MetadataAwareAspectInstanceFactory,然后获取切面类的所有没有使用Pointcut注解的方法,调用getAdvisor获取这个方法使用的切面注解,生成对应的Advisor类。 至于PointCut的处理则是再后面的getAdvisor中处理的。
9 获取切面类的Advisor
获取Advisor类的方法为getAdvisor,首先来看下这个方法的参数:
上面的参数中可以获取到切面类和切面方法,这样就可以获得一个Advisor对象,然后还需要一个切入点表达式PointCut用来匹配符合条件的方法,拦截到目标方法后,就可以执行Adivsor增强方法了。 来看看创建Advisor的过程,这里假设Method是TracesRecordAdvisor类的beforePrint方法,也就是我们测试案例中创建使用了@Before注解的切面方法:
看看getPointCut方法如何获取到exression过程需要嵌套很多步骤,这里不展开了,简单看下如何将查找到的值设置到表达式中的:
这里需要关注下上面的findAspectJAnnotationOnMethod方法:
这个方法就是查找切面方法是否使用了Before, Around, After,AfterReturning, AfterThrowing,Pointcut注解,如果使用了,则返回一个AspectJAnnotation对象,里面有一个annotation的泛型对象,这个泛型对象就是被设置为这些注解的值,而且还会获得这些注解里面配置的pointcut表达式内容,如果是引用的表达式方法,则将方法参数设置到pointcutExpression这个属性中。
解析完切面方法的注解后现在再回过头来看看如何创建一个advisor实例:
10 为切面方法创建Advice
上面方法的最后一句instantiateAdvice(this.declaredPointcut)会创建一个advice,具体是调用getAdvice方法获取:
首先来看看核心点1,上面其实已经看过了, 但是上面的方法作用仅仅是为了获取注解上的exression表达式的,这里再调用一遍就是为注解生成Advice类的,目的就是获取切面注解与AspectJAnnotation的映射类。
这个方法就是查找切面方法是否实现了Before, Around, After,AfterReturning, AfterThrowing,Pointcut注解,如果实现了,则返回一个AspectJAnnotation对象,里面有一个annotation的泛型对象,这个泛型对象就是被设置为这些注解的值。最终这些对象会被转换成下面的对象存入AspectJAnnotation中:
通过核心点1,Spring已经将注解@Before对应转换为AtBefore,@After转换成AtAfter,以此类推,都会一一映射到了核心点2的switch的条件类了,在核心点2中,会为对应的切面注解类生成Advice类。 所有的注解切面类具体实现都是由AbstractAspectJAdvice这个抽象类实现的,这个类的构造函数有三个参数:
下面是Spring为对应注解生成对应的Advice类
|--|--|
|注解类|Advice 顾问方法|
|AtBefore|AspectJMethodBeforeAdvice|
|AtAfter|AspectJAfterAdvice|
|AtAfterReturning|AspectJAfterReturningAdvice|
|AtAfterThrowing|AspectJAfterThrowingAdvice|
|AtAround|AspectJAroundAdvice|
各个注解会在不同的实际执行自身增强方法,这个部分只是生成Advice类,然会放入到缓存中,等真正生成代理时就会调用这些方法。这个在创建代理的时候需要具体拆开说,至此,Spring将使用了@Aspect注解的切面类的切面方法,都转换成了对应的Adivsor类,这个类包含了切面方法,封装后的切点匹配器PointCut以及生成切面类的实例对象,通过这个类就可以匹配到符合条件的目标类的目标方法,然后执行增强操作了。
由切面注解生成的Advice类,最终会放入到一个缓存中,当生成目标bean的时候,会将所有所以能够匹配到目标bean的advice放入到集合中,由一个实现了MethodInvocation的类统一管理调用过程,这个类后面会详细说到,这里简单分析下AspectJAfterAdvice的invoke方法,看看它的调用过程
上面的invoke方法需要一个MethodInvocation的参数,上面的Advice类除了AspectJMethodBeforeAdvice之外,都实现了这个接口,所以可以实现链式调用,这个逻辑会在创建代理的具体讲解,这里只是简单分析下,这些advice的invoke方法规定了切面方法于要增强方法的执行时机。
11 AOP代理初窥
上面一部分操作主要是处理使用了@Aspect注解的切面类,然后将切面类的所有切面方法根据使用的注解生成对应的Advisor的过程,这个Advisor包含了切面方法,切入点匹配器和切面类,也就是准好了要增强的逻辑,接下来就是要将这些逻辑注入到合适的位置进行增强,这部分的操作就是由老生常谈的代理实现的了。
创建代理前,需要先校验bean是否需要创建代理
方法很简单,主要的关注点在getAdvicesAndAdvisorsForBean和createProxy上,第一个是获取能够匹配目标类方法的Advisor集合,如果这个集合不为空,则代表该类需要被增强,需要生成代理,如果匹配不到,则表示该类并不需要被增强,无需创建代理。至于createProxy就很明显了,就是创建代理,这个方法里面决定了使用jdk代理还是cglib代理,并且用到了前面生成的Advisor实现增强功能。 这部分内容会放到下一篇文章中专门分析。
12 总结
简单总结一下,Spring AOP在初始阶段完成的主要任务:
读取配置文件阶段:
- 读取xml文件遇到
<aop:aspectj-autoproxy/>
标签时,找到命名空间处理器AopNamespaceHandler,然后找到处理该标签的类AspectJAutoProxyBeanDefinitionParser - 通过AspectJAutoProxyBeanDefinitionParser的parse方法,将AspectJAwareAdvisorAutoProxyCreator注册到容器的声明周期中。
创建bean阶段: - 执行AspectJAwareAdvisorAutoProxyCreator的postProcessBeforeInstantiation校验目标类是否是Aspect类和AOP基础类以及是否需要跳过不需要执行代理的类
- 获取beanDefinitions中所有使用了Aspect注解的类,然后将切面方法根据使用的注解生成Advisor类放入到缓存(关键)
- 调用AspectJAwareAdvisorAutoProxyCreator的postProcessAfterInitialization的方法,对需要增强的类创建代理。
文章转载自公众号: 架构与思维
