0.What’s this?
这是一篇关于《Spring源码深度解析》的笔记
第七章 AOP
AOP -> 面向切面编程,指横向地关注程序执行过程中发生的公共行为,如日志、安全检测等,将其抽取出来以减少程序中的重复代码。
7.1 动态AOP的使用
- 创建Advisor,用注解@Aspect标注,用@Pointcut指定切点,再用@Before、@After、@Test等注解标注方法在切点附近的执行时机
- 在配置文件中声明aop:aspectj-autoproxy标签,并添加bean和测试类
- 执行切点方法,查看Advisor中的方法是否执行
7.2 动态AOP自定义标签
在解析配置文件时,遇到aop:aspectj-autoproxy标签,就会使用解析器AspectJAutoProxyBeanDefinitionParser进行解析。
7.2.1 注册AnnotationAwareAspectJAutoProxyCreator
所有解析器,因为是对BeanDefinitionParse的同意实现,入口都是从parse函数开始的,AspectJAutoProxyBeanDefinitionParser的parse函数中,
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary方法是关键逻辑的实现部分:
- 注册或升级AnnotationAwareAspectJAutoProxyCreator,它可以根据@Point注解去定义切点,自动代理相匹配的bean
- 调用useClassProxyingIfNecessary方法,处理proxy-target-class以及expose-proxy属性。
7.3 创建AOP代理
AnnotationAwareAspectJAutoProxyCreator实现了BeanPostProcessor接口,当这个bean被加载的时候,会自动调用postProcessAfterInitialization方法。在该方法中,会根据bean是否需要被代理,决定是否需要封装指定的bean,该操作在wrapIfNecessary方法中执行。
- 判断bean是否已经处理过,如果在targetSourcedBeans中包含这个bean的名字,则说明已处理过,直接return
- 判断bean是否需要处理,如果nonAdvisedBeans中包含这个bean的名字,则说明无需处理,直接return
- 判断bean是否需要设置代理,如果该bean代表一个基础设施类,则不应设置代理,如果在配置中指定了这个bean不需要代理,则将其添加到nonAdvisedBeans中并直接return
- 尝试通过getAdvicesAndAdvisorsForBean方法获取当前bean的增强方法,如果获取到了增强方法,则针对增强创建代理。创建代理的过程在createProxy方法中完成
7.3.1 获取增强器
获取增强器的功能在findEligibleAdvisors方法中完成,通过注解实现AOP时,这个方法的实现是由AnnotationAwareAspectJAutoProxyCretor完成的,该方法间接继承了AbstractAdvisorAutoProxyCreator,在实现获取增强的方法中除了保留父类的获取配置文件中定义的增强外,同时添加了获取bean的注解增强的功能。其大致过程包括:获取所有的beanName、遍历所有beanName,找出声明AspectJ注解的类、对标记为AspectJ注解的类进行增强器提取(getAdvisors),最后将提取结果存入缓存中。
advisorFactory.getAdvisors
增强器的提取在getAdvisors方法中完成,具体有两个步骤:获取切点的注解以及根据注解信息生成增强
- 切点信息获取:获取指定注解的表达式信息,如注解类型、注解内的表达式等
- 根据切点信息,调用Advisor类的InstantiationModelAwarePointcutAdvisorImpl方法,根据不同的注解类型封装不同的增强器
SyntheticInstantiationAdvisor
如果寻找的增强器不为空,而且又配置了增强的延迟初始化,那么需要在首位加入同步实例化增强器
getDeclareParentsAdvisor
用于获取DeclareParents注解,DeclareParents主要用于引介增强的注解形式的实现,而其实现方式与普通增强类似,但会使用DeclareParentsAdvisor对功能进行封装
7.3.2 获取匹配的增强器
针对于每个bean,要挑选出适合当前bean的增强器,即满足我们配置的通配符的增强器,该过程在findAdvisorsThatCanApply方法中实现,其主要功能是寻找所有适用于当前bean的增强器,并分开处理引介增强和普通增强。对于是否适用。
7.3.3 创建代理
createProxy:
- 获取当前类中的相关属性
- 添加代理接口
- 封装Advisor并加入ProxyFactory中
- 设置要代理的类
- 根据需要定制代理类
- 进行获取代理的操作,调用proxyFactroy的getProxy方法获取代理
getProxy:
public Object getProxy(ClassLoader classLoader){
return createAopProxy().getProxy(classLoader);
}
进行代理的创建:createAopProxy方法
主要是根据一些条件来确定创建JDKProxy还是CglibProxy。根据if条件,有三个方面的因素:
- optimize:用来控制通过CGLIB创建的代理是否使用激进优化策略,只推荐在完全了解AOP代理的优化的情况下使用。对JDK动态代理无效。
- proxyTargetClass:这个属性为true时,目标本身被代理而不是目标类的接口,此时CGLIB代理将被创建。设置方式:
<aop:aspextj-autoproxy proxy-target-class = “true” /> - hasNoUserSuppliedProxyInterfaces:是否存在代理接口
JDK与Cglib方式的总结:
- 如果目标对象实现了接口,默认情况下采用JDK的动态代理实现AOP
- 如果目标对象实现了接口,可采用添加CGLIB库或指定proxy-target-class的方法强制使用CGLIB实现AOP
- 如果没有实现接口,则必须采用CGLIB库,Spring会自动在两者之间转换
两者的区别:
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类
- CGLIB时针对类实现代理,主要是对指定的类生成子类,覆盖其中的方法。因为这个实现过程本质上是继承,所以类或方法最好不声明为final的
创建代理完成后,进行代理的获取
- JDK动态代理
在整个代理创建过程中,最重要的是InvocationHandler的创建,在自定义InvocationHandler中需要重写三个方法:构造函数、invoke方法和getProxy方法。JDK动态代理就是创建了InvocationHandler,并重写了invoke方法。 - CGLIB动态代理
CGLIB底层通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类。完成CGLIB代理的类是委托给Cglib2AopProxy类去实现的。这种实现方式与invoke方法大同小异,都是首先构造链,然后封装此链进行串联调用。
7.4 静态AOP
静态AOP即加载时织入(Load-Time Weaving,LTW),指在虚拟机载入字节码文件时动态织入AspectJ切面。使用静态代理需要对项目进行一些改动:
- 在Spring全局配置文件中加入LWT开关:<context:load-time-weaver />
- 加入aop.xml文件,通过aspects标签和aspect子标签定义切面类
- 加入启动参数:-javaagent:\org.springframework.instrument.jar
7.5 创建静态AOP代理
AOP静态代理主要是在虚拟机启动时通过改变目标对象字节码的方式来完成对目标对象的增强,相比动态代理具有更高的效率。
7.5.1 Instrumentation的使用
java.lang.Instrument在java 1.5时引入,通过它可以实现一个Java agent,通过次agent来修改类的字节码从而改变一个类。
- 实现ClassFileTransformer接口,实现其中的doMethod方法来修改某个方法的字节码
- 编写agent类,在其中实例化我们自己实现的ClassFileTransformer,并在JVM启动时,应用加载前先调用agent类中的某个方法。
- 打包agent
- 打包应用,通过JVM参数-javaagent:xx来加载之前打包的agent,执行我们定义好要执行的agent中的方法。
7.5.2 自定义标签
在Spring中如果需要使用AspectJ的功能,首先需要在配置文件中加入配置:<context:load-time-weaver />,在ContextNamespaceHandler中的init方法内,会调用registerBeanDefinitionParser方法,对load-time-weaver标签进行解析。该方法调用到了LoadTimeWeaverBeanDefinitionParser类的doParse方法,它的核心作用时注册一个AspectJ的处理类:AspectJWeavingEnabler,注册流程:
- 检查是否开启了AspectJ,标签<context:load-time-weaver />有一个属性aspectj-weaving,取值有on, off和autodetect,默认为autodetect,默认情况下Spring会自动寻找META-INF/aop.xml是否存在,从而推断是否可以使用AspectJ功能
- 将AspectJWeavingEnabler封装在BeanDefinition中继续宁注册。当Spring读取到自定义标签<context:load-time-weaver />时,会产生一个bean,其id为loadTimeWeaver,class为DefaultContextLoadTimeWeaver,也就是完成了这个类的注册,之后在AbstractApplicationContext中的prepareBeanFactory方法内进行LoadTimeWeaverAwareProcessor的注册,激活整个AspectJ的功能
7.5.3 织入
首先进入LoadTimeWeaverAwareProcessor中的postProcessAfterInitialization方法,加载过程会通过实现的BeanClassLoaderAware接口的方法,保证初始化过程中调用AbstractAutowireCapableBeanFactgory的invokeAwareMethods方法时,同时调用setBeanClassLoader方法,完成两个工作:
- 将AspectJWeavingEnabler类型的bean中的loadTimeWeaver属性初始化为DefaultContextLoadTimeWeaver类型的bean
- 将DefaultContextLoadTimeWeaver类型的bean中的loadTimeWeaver属性初始化为InstrumentationLoadTimeWeaver类型的bean
这个过程完成后,Instrumentation属性已经被初始化为代表着当前虚拟机的实例了,接下来就可以使用此属性进行操作了。