Spring源码深度解析笔记(7)——AOP

我能都知道,使用面向对象编程有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、安全检测等,我们只有在每一个对象里应用功能行为,这样程序中产生了大量重复代码,程序就不便于维护了,所以就有了一个对面向对象编程的补充,即面向方面编程(AOP),AOP所关注的方向是横向,不同于OOP的纵向。

Spring中提供了AOP的实现,但是在低版本的Spring中定义了一个切面比较麻烦,需要实现特定接口,并进行一些较为复杂的配置。低版本Spring AOP的配置是被批评最多的地方。Spring听取了这方面的批评声音,并决心彻底改变这一现状。Spring 2.0中,Spring AOP已经焕然一新了,可以采用@AspectJ非常容易的定义一个切面,不需要实现任何接口。

Spring 2.0采用@AspectJ注解对POJO进行标注,从而定义一个包含切点信息和增强横切面逻辑的切面。Spring 2.0可以将这个切面植入到匹配的目标Bean中。@AspectJ注解使用AspectJ切点表达式语法进行切点定义,可以通过切点函数、运算符、通配符等高级功能进行切点定义,拥有强大的连接点描述能力。

7.1 动态AOP使用示例

  1. 创建用于拦截的bean,在实际工作中,此bean可能满足业务需求的核心逻辑。
  2. 创建Advisor,Spring中摒弃了原始的繁杂配置方式而采用@AspectJ注解对POJO进行标注,使AOP的工作大大简化。
  3. 创建配置文件,XML是Spring的基础。尽管Spring一再简化配置,并且大有使用注解取代XML配置之势,但是无论如何,至少现在XML还是Spring的基础。要在Spring中开启AOP功能,还需要在配置文件中声明。<aop:aspectj-autoproxy />
  4. 测试

那么,Spring究竟是如何实现AOP的呢,首先我们知道,Spring是否支持注解的AOP由一个配置文件控制的,也就是<aop:aspectj-autoproxy />,当配置文件中声明了这句配置的时候,Spring就会支持注解的AOP,那么分析就是从这句注解开始的。

7.2 动态AOP自定义标签

之前讲过Spring中的自定义注解,如果声明了自定义的注解,那么就一定会在程序中的某个地方注册了对应的解析器。搜索整个代码后发现了在AopNamespaceHandler中对应这一段函数

public void init(){
	......
	registerBeanDefinitionParser("aspectj-autoproxy",new AspectJAutoProxyBeanDefinitionParser ());
	......
}

在解析文件的时候,一旦遇到aspectj-autoproxy注解时就会使用解析器AspectJAutoProxyBeanDefinitionParser进行解析,所以来看看AspectJAutoProxyBeanDefinitionParser 的内部实现。

7.2.1 注册AnnotationAwareAspectJAutoProxyCreator

所有解析器,因为是对BeanDefinitionParser接口的统一实现,入口都是从parse函数开始的AspectJAutoProxyBeanDefinitionParser的parse函数如下

// 注册AnnotationAwareAspectJAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAwareAspectJAutoProxyCreatorIfNecessary(parserContext,element);
// 对于注解中子类的处理
extendBeanDefinition(element, parserContext);

其中registerAspectJAnnotationAutoProxyCreatorIfNecessary的逻辑实现如下:

// 注册或升级AutoProxyCreator定义beanName为org.Springframework.aop.config.internalAutoProxyCreator的beanDefinition
AopConfig.registerAspectJAnnotationAutoProxyCreatorIfNecessary(...);
// 对于proxy-target-class以及expose-proxy属性的处理
useClassProxyIfNecessary(...);
// 注册组件并通知,便于监听器做进一步处理,其中beanDefinition的className为AnnotationAwareAspectJAutoProxyCreator
registerComponentIfNecessary(beanDefinition,parserContext);

registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中主要完成了3件事,基本上每一行就是一个完整的逻辑。

  1. 注册或者升级AnnotationAwareAspectJAutoProxyCreator,对于AOP的实现,基本上都是靠AnnotationAwareAspectJAutoProxyCreator去完成的,它可以根据@Point注解定义的切面来自动代理相匹配的bean,但是为了配置简便,Spring使用了自定义配置来帮助自动注册AnnotationAwareAspectJAutoProxyCreator,其注册过程就是在这里实现的。
    registerOrEscalateApcAsRequired(...)
    // 如果已经存在了自动代理创建器且存在的自动代理创建器与现在的不一致那么需要根据优先级来判断到底使用哪个
    // 如果已存在自动代理创建器并且与将要创建的一致,那么无需再次创建
    以上的逻辑实现了注册AnnotationAwareAspectJAutoProxyCreator类的功能,同时在这里还涉及到一个优先级的问题,如果已经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底需要使用哪个。

  2. 处理proxy-target-class以及expose-proxy属性, useClassProxyIfNecessary实现了proxy-target-class属性以及expose-proxy属性的处理
    // 对于proxy-target-class属性的处理 AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(...)
    // 对于expose-proxy属性的处理 AopConfigUtils.forceAutoProxyCreatorToExposeProxying(...)
    // 强制使用的过程其实是一个属性设置的过程

proxt-target-class:Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议尽量使用JDK动态代理),如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都会被代理。如该目标对象没有实现任何接口,则创建一个CGLIB代理。如果强制使用CGLIB代理,(例如希望代理目标对象的所有方法,而不只是实现自接口的方法)那也可以,但是需要考虑一下两个问题。

  1. 无法通知(advise)Final方法,因为它们不能被覆写。
  2. 需要将CGLIB二进制发行包放在classpath下面。

与之相对的,JDK本身就提供了动态代理,强制使用CGLIB代理需要将< aop : config>的proxy-target-class属性为true

JDK动态代理和CGLIB动态代理在实际的使用过程中会有一些细微差别:

JDK动态代理:其代理对象必须是某个接口的实现,它通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层依靠ASM操作字节码实现的,性能比JDK强。
expose-proxy:有时候目标内部子午调用将无法实现实施切面中的增强。

7.3 创建AOP代理

以上讲解了通过自动配置完成了对AnnotationAwareAspectJAutoProxyCreator类型的自动注册,那么这个类到底做了什么工作来完成AOP的操作呢?

从AnnotationAwareAspectJAutoProxyCreator类的层次结构可知,AnnotationAwareAspectJAutoProxyCreator实现了BeanPostProcessor接口,而实现BeanPostProcessor后,当Spring加载这个Bean时会在实例化前调用其postProcessAfterInitialization方法,而我们对AOP逻辑的分析也就此开始。

在父类AbstractAutoProxyCreator的postProcessAfterInitialization中的代码中:

// 根据给定的bean的class和name构建出个key,格式为:beanClassName_beanName
// 如果它适合被代理,则需要封装指定bean
return wrapIfNecessary(bean,beanName,cacheKey);
// wrapIfNecessary(bean,beanName,cacheKey)
// 如果已经处理过,则直接返回bean实例
// 如果无需增强则直接返回bean实例
// 判断给定的bean类是否代表一个基础设施类,如果是基础设施类则不应该被代理,或者配置了指定bean不需要代理
// 如果存在增强方法则创建代理
Object[] specifcInterceptors = getAdviceAndAdcisorForBean(bean.getClass());
// 如果获取到了增强则需要针对增强创建代理
Object proxy = createProxy(bean.getClass(),beanName,specifcInterceptors ,new SingletonTargetSource(bean));

真正开始之前还需要经过一些判断,而真正创建代理的代码是从getAdvicesAndAdvisesForBean开始的。

创建代理主要包含两个步骤:

  1. 获取增强方法或者增强器
  2. 根据获取的增强进行代理

首先来看看获取增强方法的实现逻辑

getAdvicesAndAdvisorsForBean(...)

findEligibleAdvisors(...)

findCandidateAdvisors(...)

findAdvisorsThatCanApply(...)

对于指定bean的增强方法的获取一定是包含两个步骤,获取所有的增强以及寻找所有增强中适用于bean的增强并应用,那么findCandidateAdvisors和findAdvisorsThatCanApply便做了这两件事情。当然,如果无法找到对应的增强器便返回DO_NOT_PROXY,其中DO_NOT_PROXY = null。

7.3.1 获取增强器

由于使用了注解进行了AOP,所以对于findCandidateAdvisors的实现其实是由AnnotationAwareAspectJAutoProxyCreator类完成的。

// 当使用注解方式配置AOP的时候并不是丢弃了对XML配置的支持
// 在这里调用父类方法加载配置文件中的AOP声明
super.findCandidateAdvisors();
// 创建代理工厂
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());

AnnotationAwareAspectJAutoProxyCreator间接继承了AbstractAdvisorAutoProxyCreator,在实现获取增强方法中除了保留父类获取配置文件中定义的增强外,同时添加了获取Bean的注解增强的功能,那么其实现正是由this.aspectJAdvisorsBuilder.buildAspectJAdvisors()来实现的。

该方法的主要实现如下:

  1. 获取所有beanName,这一步骤中所有在beanFactory中注册的Bean都会被提取出来。
  2. 遍历所有的beanName,并找出声明AspectJ注解的类,进行进一步处理。
  3. 对标记AspectJ注解的类进行增强器的提取
  4. 将提取结果加入缓存
// 获取所有的beanName
// 循环所有的beanName找出对应的增强方法
// 不合法的beanName则略过,由子类定义规则,默认返回true
// 获取对应的bean的类型
// 如果存在AspectJ注解,则解析标记AspectJ注解中的增强方法
this.advisorFactory.getAdvisors(factory);
// 记录在缓存中

至此,已经完成了Advisor的提取,在上面的步骤中最为重要也是最为繁杂的就是增强器的获取。而这一功能委托给了getAdvisor方法去实现。

// 获取标记为AspectJ的类
// 获取标记为AspectJ的name
// 验证
// 声明为Pointcut的方法不处理
Advisor advisor = getAdvisor(...);
// 如果寻找的增强器不为空而且又配置了增强器延迟初始化那么需要在首位加入同步实例化增强器
// 获取DeclaerParents注解

函数中首先完成了对增强器的获取,包括获取注解以及根据注解生成增强的步骤,然后考虑在配置中可能会将增强配置成延迟初始化,那么需要在首位加入同步实例化增强器以保证增强使用之前的实例化,最后是对DeclaerParents注解的获取。

1. 普通增强器的获取

普通增强器的获取逻辑是通过getAdvisor方法实现的,实现步骤包括对切点的注解的获取以及根据注解信息生成增强。

// 切点信息的获取
AspectJExpressionPointcut ajexp = getPointcut(candidateAdviceMethod,aif.getAspectMetadata().getAspectClass());
// 根据切点信息生成增强器
InstantiationModelAwarePointCutAdvisorImpl(...);
  1. 切点信息的获取,所谓切点信息的获取就是指注解的表达式信息的获取,如@Before(“test()”)。
    // 获取方法上的注解
    AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(...)
    // 使用AspectJExpressionPointcut实例封装获取的信息
    提取得到的注解中的表达式
  2. 根据切点信息生产增强。所有的增强都是有Advisor的实现类InstantiationModelAwarePointCutAdvisorImpl统一封装的。
    instantiateAdvice(this.DeclaerPointcut);
    在封装的过程中只是简单的将信息封装在类的实例中,所有的信息单纯的赋值,在实例初始化的过程中还完成了对于增强器的初始化。因为不同的增强器所体现的逻辑是不同的,比如@Before(“test()”)和@After(“test()”)标签的不同就是增强器增强的位置不同,所以就需要不同的增强器来完成不同的逻辑,而根据注解中的信息初始化对应的增强器就是在instantiateAdvice函数中实现的。
2. 增加同步实例化增强器

如果寻找的增强器不为空而且又配置了增强延迟初始化,那么就需要在首位加入同步实例化增强器。同步实例化增强器SyntheicInstantiationAdvisor

3. 获取DeclaerParents注解

DeclaerParents主要是用于引介增强的注解形式的实现,而其实现方式与普通增强器很类似,只不过使用DeclaerParentsAdvisor对功能进行封装。

7.3.2 寻找匹配的增强器

前面的函数中已经完成了所有增强器的解析,但对于所有的增强器来讲,并不一定都适用于当前的Bean,还要挑选出适合的增强器,也就是满足我们配置的通配符的增强器。具体的实现在findAdvisorThatCanApply中的AopUtils.findAdvisorThatCanApply(…)

// 首先处理引介增强器
// 引介增强器已处理
// 对于普通bean的处理
7.3.3 创建代理

在获取了所对应bean的增强器后,便可以进行代理的创建了。

// 获取当前类中相关的属性
// 决定对于给定的bean是否使用targeClass而不是它的接口代理,检查proxyTargetClass设置以及preserveTargetClass属性
// 添加代理接口
// 加入增强器
// 设置要代理的类
// 定制代理
// 用来控制代理工厂被配置后,是否允许修改通知。默认值为false(即在代理被配置之后,不允许修改代理的配置)。
return proxyFactory.getProxy(this.proxyClassLoarder);

对于代理类的创建以及处理,Spring委托给了ProxyFactory去处理,而在此函数中主要是对ProxyFactory的初始化操作,进而对真正的创建代理做准备,这些初始化操作包含如下内容。

  1. 获取当前类中的属性
  2. 添加代理接口
  3. 封装Advisor并加入到ProxyFactory中
  4. 设置要代理的类
  5. 当然在Spring中还为子类提供了定制的函数customizeProxyFactory,子类可以在此函数中进行对ProxyFactory的进一步封装
  6. 进行获取代理操作

其中,获取Advisor并加入到ProxyFactory中以及创建代理是两个相对繁琐的过程,可以通过ProxyFactory提供的addAdvisor方法直接将增强器置入代理创建工厂中,但是将拦截器封装为增强器还需要一定的逻辑。

// 解析注册所有的interceptName
// 加入拦截器
// 拦截器进行封装转化为Advisor(this.advisorAdapterRegistry.wrap(...))
// 如果要封装的对象本身就是Advisor类型的那么就无需在做过多处理
// 因为此封装方法只对Advisor和Advice两种类型有效,如果不是将不能封装
// 如果是MethodIntercepter类型则使用DefaultPointcutAdvisor封装
// 如果存在Advisor的适配器那么也同样需要进行封装

由于Spring中涉及过多的拦截器、增强器、增强方法等方式来对逻辑进行增强,所以非常有必要统一封装成Advisor来进行代理的创建,完成了增强的封装过程,那么解析最重要的一步就是代理的创建和获取了。

public Object getProxy(ClassLoarder classLoarder){
	return createAopProxy().getProxy(classLoarder);
}
  1. 创建代理
return getAopProxyFactory().createAopProxy(this);

到此已经完成了代理的创建,不管我们之前是否阅读过Spring的源码,但是都或多或少的听说过对于Spring的代理中JDKProxy的实现好CGlibProxy的实现,Spring是如何选取的呢?现在我们从源码的角度分析,看看到底Spring是如何选择代理方法方式的。

if(config.isOptimize) || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)

从if的判断条件可以看到3个方面影响着Spring的判断

  1. Optimize:用来控制通过CGLIB创建的代理是否使用激进的优化策略。除非完全了解AOP代理如何处理优化的,否则不推荐用户使用这个属性,目前这个属性仅用于CGLIB代理,对于JDK动态代理无效。
  2. proxyTargetClass:这个属性为true时,目标类本身被代理而不是目标类的接口,如果这个属性值被设为true,CGLIB代理被创建,设置方式:< aop:aspectj-autoproxy proxy-target-class=“true”/>
  3. hasNoUserSuppliedProxyInterfaces:是否存在代理接口

下面是对JDK和CGlib方式的总结:

1.如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2.如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3.如果目标对象没有实现接口,必须使用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换

如何强制使用CGLIB实现AOP?

  1. 添加CGLIB库,Spring_HOME/cglib/*.jar
  2. 在Spring配置文件中加入< aop:aspectj-autoproxy proxy-target-class=“true”/>

JDK动态代理和CGLIB字节码生成的区别?

  1. JDK动态代理只能对实现了接口的类生产代理,而不能针对类。
  2. CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final。
  1. 获取代理
    确定了使用哪种代理方式后便可以进行代理的创建了,但是创建之前,有必要回顾一下两种方式的使用方法。
    (1). JDK代理使用示例。

创建业务接口,业务对外提供的接口,包含着业务可以对外提供的功能;
创建业务接口实现类;
创建自定义的InvocationHandler,用于对接口提供的方法进行增强;
最后进行测试,验证对于接口的增强是否起作用
// 实例化目标对象
// 实例化InvocationHandler
// 根据目标对象生成代理对象
// 调用代理对象的方法

Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的,在整个创建过程中,对于InvocationHandler的创建是最为核心的,在自定义的InvocationHandler中需要重写3个函数。

  1. 构造函数,将代理的对象传入。
  2. invoke方法,此方法中实现了AOP增强的所有逻辑。
  3. getProxy方法,此方法千篇一律,但是必不可少。

那么,Spring中的JDK代理实现是不是也是这么做的呢?继续之前的跟踪,到达了JdkDynamicAopProxy的getProxy。
通之前的实例可知,JDKProxy的使用关键是创建自定义的InvocationHandler,而InvocationHandler中包含了需要覆盖的函数getProxy,而当前的方法正式完成这个操作。再次确认一下JdkDynamicAopProxy也确实实现了InvocationHandler接口,那么可以推断出,在JdkDynamicAopProxy中一定有个invoke函数,并且JdkDynamicAopProxy会把AOP的核心逻辑写在其中。

// equals方法的处理
// hash方法的处理
// 有时候目标对象内部的自我调用将无法实施切面中的增强则需要通过此属性暴露代理
this.advised.exposeProxy
// 获取当前方法的拦截器链
// 如果没有发现任何拦截器那么直接调用切点方法
// 将拦截器封装在ReflectiveMethodInvocation中,以便于使用其process进行链接表用拦截器
invocation = new ReflectiveMethodInvocation(...);
// 执行拦截器链
retVal = invocation .proceed();
// 返回结果

上面的函数的最主要的工作就是创建了一个拦截器链,并使用ReflectiveMethodInvocation类进行了链的封装,而在ReflectiveMethodInvocation类的proceed实现了拦截器的逐一调用,那么在proceed方法中是怎样实现前置增强在目标方法钱调用后置增强在目标方法后调用的逻辑的呢?

// 执行完所有增强后执行切点方法
// 获取下一个要执行的拦截器

在proceed方法中,或许代码逻辑没有我们想象的那么复杂,ReflectiveMethodInvocation中的主要职责是维护了链接调用的计数器,记录着当前调用链接的位置,以便链接可以有序的进行下去,那么在这个方法中并没有我们之前设想的维护这各种增强的顺序,而是将此工作委托给了个各个增强器,是增强器在内部进行逻辑实现。

(2). CGLIB使用实例
CGLIB是一个强大的高性能代码生成包,它广泛的被许多AOP框架使用,例如Spring和dynaop,为他们提供方法的Intercepter,最流行的ORMapping工具Hibernate也使用CGLIB来代理单端single-ended(多对一和一对一)关联(对集合的延迟抓取是采用其他机制实现的)。EasyMock和jMock是通过模仿mock对象来测试Java代码的包。它们通过使用CGLIB来为那些没有接口的类创建模仿mock对象。

CGLIB包的底层通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类,除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成Java字节码,当然不鼓励直接使用ASM,因为他要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

完成CGLIB代理的类是委托给Cglib2AopProxy类去实现的,Cglib2AopProxy的入口应该是getPorxy,也就是说在Cglib2AopProxy类的getProxy方法中实现了Enhancer的创建及接口封装。

// 验证class
// 创建及配置Enhancer
// 设置拦截器
getCallBacks(rootClass)
// 生成代理类以及创建代理

以上函数完整的阐述了一个创建Spring中的Enhancer的过程,可以参考Enhancer的文旦查看每个步骤的含义,这里最重要的是通过getCallbacks方法设置拦截器链。

// 将拦截器封装在DynamicAdvisedIntercepter中
Callback aopIntercepter = new DynamicAdvisedIntercepter(this.advised);
// 将拦截器链加入Callback中

在getCallback中Spring考虑了很多情况,但是对于我们来说,只需要理解最常用的就可以了,比如将advised属性封装在DynamicAdvisedIntercepter并加入到callbacks中,这么做的目的是什么呢,如何调用呢?在前面的实例中,我们了解到CGLIB中对于方法拦截是通过自定义拦截器(实现MethodIntercepter接口)加入到Callback中并在调用代理时直接激活拦截器中的intercept方法来实现的,那么getCallback中正式实现了之一目的,DynamicAdvisedIntercepter继承自MethodIntercepter,加入到Callback中后,在再次调用代理是会直接调用DynamicAdvisedIntercepter中intercept方法,由此推断,对于CGLIB方式实现的dialing,其核心逻辑必然会是在DynamicAdvisedIntercepter中的intercept中。

// 获取拦截器链
// 如果拦截器链为空,则直接激活原方法
// 进入链

上述方法的实现与JDK方式实现代理中的invoke方法大同小异,都是首先构造链,然后再封装此链进行串联调用,稍微有些区别就是在JDK中直接构造ReflectiveMethodInvocation,而在cglib中使用CglibMethodInvocation。CglibMethodInvocation继承自ReflectiveMethodInvocation,但是process方法并没有重写。

7.4 静态AOP使用示例

加载时织入指的是在虚拟机载入字节码文件时动态织入AspectJ切面,Spring框架的值添加为AspectJ LTW在动态织入时,提供了更细粒度的控制。使用Java的代理能使用一个叫“Vanlia”的AspectJ LTW,这需要在启动JVM时将每个JVM参数设置为开。这种JVM范围的设置在一些情况下或许不错,但是通常情况下显然有些粗粒度。而用Spring的LTW能让你在per-ClassLoarder的基础上打开LTW,这显然更加细粒度并且对“单JVM多应用”的环境更具有意义(例如一个典型应用服务器环境中),另外在某些环境下,这能让你使用LTW而不对应用服务器的启动脚本做任何改动,不然则需要添加相关配置,开发人员只需要简单修改应用上下文的一个或几个文件就能使用LTW,而不需要依靠那些管理者部署配置。

我们还是以之前的AOP示例为基础,如果想从动态代理的方式改成静态代理的方法需要做如下改动。

  1. Spring全局配置文件的修改,加入LWT开关。
  2. 加入aop.xml。在class目录下的META-INF文件夹下建立aop.xml。
  3. 加入启动参数。
7.5 创建AOP静态代理

AOP静态代理主要是在虚拟机启动时通过改变目标对象字节码的方式来完成对目标对象的增强,它与动态代理相比具有更高的效率,因为在动态代理调用过程中,还需要一个动态创建代理类并代理目标对象的步骤,而静态代理则是在启动时完成了字节码增强,当系统再次调用目标类时与调用正常类并无差别,所以效率上会相对高一些。

7.5.1 Instrumentation使用

Java在1.5引入java.lang.Instrument,你可以由此实现一个Java agent,通过此agent来修改类的字节码来改变一个类,通过Java Instrument实现一个简单的profiler,当然Instrument并不限于profiler,Instrument可以做很多事情,它类似于一个更低级、更松耦合的AOP,从底层改变一个类的行为。

以计算一个方法的运行时间为例,在方法的开头和结尾不得不在所有需要监控的方法的开头和结尾写入重复的代码,而Java Instrument使得这一切更干净。

  1. 写ClassFileTransformer类,实现ClassFileTransformer,编写修改字节码方法;
  2. 编写agent类,JVM启动时在应用加载前会调用PerfMonAgent.permain,然后PerfMonAgentPerfMonAgent.permain实例化一个定制的ClassFileTransformer类,即PerMonXformer并通过inst.addTransformer(trans)把PerfMonXformer的实例加入Instrumentation实例(有JVM传入),这就使得应用中的类加载时,PerfMonXformer.transform都会被调用,你在此方法中就可以改变加载的类,还可以使用JBoss的Javaassist轻易的改变类的字节码;
  3. 打包agent,引入相关的Jar包;
  4. 打包应用。

由执行结果可以看出,执行顺序以及通过改变字节码加入监控代码确实生效了,而且通过Instrument实现agent使得监控代码和应用代码完全隔离了。

在Spring中的静态AOP直接使用了AspectJ提供的方法,而AspectJ又是在Instrument基础上进行的封装,至少在AspectJ中会有如下功能:

  1. 读取META-INF/aop.xml;
  2. 将aop.xml中定义的增强器通过定义的ClassFileTransformer织入对应的类中。

Spring 是直接使用AspectJ,也就是将动态代理的任务直接委托给了AspectJ,那么,Spring是怎么嵌入AspectJ的呢?

7.5.2 自定义标签

在Spring中如果使用AspectJ的功能,首先要做的第一步就是在配置文件中加入配置:<context:load-time-weaver />,引入AspectJ的入口便是这里。可以通过查找load-time-weaver来找到对应的自定义命名处理类。

通过定位找到对应类ContextNameSpaceHandler,有一段函数
registerBeanDefinitionParser("load-time-weaver",new LoadTimeWeaverBeanDefinitionParser());

继续跟进LoadTimeWeaverBeanDefinitionParser,作为BeanDefinitionParser接口的实现类,它的核心逻辑是从parse函数开始的,而经过父类的封装,LoadTimeWeaverBeanDefinitionParser类的核心实现被转移到了doParse函数中。

1. isAspectJweavingEnabled(...);
2. weavingEnablerDef.setBeanClassName(...);
3. isBeanConfigurAspectEnabled(...)

其实之前在分析动态AOP也就是在分析配置中已经提到了自定义配置的解析流程,对于aspectJ的解析无非是以标签作为标志,进行相关处理类的注册,对于自定义标签load-time-weaver其实是起到了同样的作用。
上面函数的核心作用其实就是注册一个对于AspectJ处理的类AspectJweavingEnabled,它的注册流程总结起来如下:

  1. 是否启动AspectJ,之前提到的在配置文件中添加load-time-weaver便相当于加入了Aspect开关。但是不是配置了这个标签就意味着开启了AspectJ功能,这个标签还有一个属性aspect-weaving,这个属性有3个备选值,on、off、autodetect,默认是autodetect,也就是说,如果只使用了load-time-weaver,那么Spring会帮助我们检测是否可以使用AspectJ功能,而检测的依据便是文件MATE-INF/aop.xml是否存在。
  2. 将AspectJweavingEnabled封装在BeanDefinition中注册,当通过AspectJ功能验证后便可以进行AspectJweavingEnabled的注册了,注册的方法很简单,无非是将类路径注册在新初始化的RootBeanDefinition中,在RootBeanDefinition的获取时会转换成对应的class。尽管在init方法注册了AspectJweavingEnabled,但对于标签本身Spring也会以bean的形式保存,也就是当Spring解析到load-time-weaver标签的时候也会产生一个bean,当Spring在读取到自定义标签load-time-weaver会产生一个bean,而这个bean的id为loadTimeWeaver,class为DefaultContextLoadTimeWeaver,也就是完成了DefaultContextLoadTimeWeaver类的注册。完成了以上的注册功能后,并不意味着在Spring中就可以使用AspectJ了,因为还以一个很重要的步骤忽略了,就是loadTimeWeaverAwareProccessor的注册。在AbstractApplicationContext中的perpareBeanFactory函数实在容器初始化时调用的,也就是说只有注册了loadTimeWeaverAwareProccessor才会激活整个AspectJ的功能。
7.5.3 织入

当完成了所有AspectJ的准备工作便可以进行织入分析,首先还是LoadTimeWeaverAwareProccessor开始的,LoadTimeWeaverAwareProccessor实现了BeanPostProcessor方法,那么对于BeanPostProcessor接口来讲,postProcessBeforeInitalization与postProcessAfterInitalization有着其特殊的意义,也就是所有的bean的初始化之前和之后都会分别调用对应的方法,那么LoadTimeWeaverAwareProccessor中的postProcessBeforeInitalization函数完成了什么样的逻辑呢?

在postProcessBeforeInitalization中的postProcessBeforeInitalization函数中,因为最开始的if判断注定这个后处理器只对LoadTimeWeaverAware类型的bean起作用,而纵观所有的bean,实现loadTimeWeaverAware接口的类只用AspectJWeavingEnableder。

当在Spring中调用AspectJWeavingEnableder时,this.loadTimeWeaver尚未被初始化,那么,直接调用beanFactory.getBean方法获取对应的DefaultContextLoadTimeWeaver类型的bean,并将其设置为AspectJWeaverEnabler类型bean的loadTimeWeaver属性中,当然AspectJWeaverEnabler同样实现了BeanClassLoaderAware以及Ordered接口,实现BeanClassLoaderAware接口保证了bean初始化时会调用AbastractAutowireCapableBeanFactory的invokeAwareMethods的时候将beanClassLoader赋值给当前类。而实现ordered接口则保证在实例化bean当前bean会最先初始化。

而DefaultContextLoadTimeWeaver类用同时实现loadTimeWeaver、BeanClassLoaderAware以及DisposableBean。其实DisposableBean接口保证在bean销毁时会调用destroy方法进行bean的清理,而BeanClassLoaderAware接口保证bean在初始化时滴啊用AbstractAutowireCapableBeanFactory的invokeAwareMethods时调用setBeanClassLoader方法。

this.loadTimeWeaver = new InstrumentationLoadTimeWeaver(classLoader);

这句代码不仅仅是实例化一个InstrumentationLoadTimeWeaver类型的实例,而且在实例化还做了一些额外的操作。

在实例化的过程中会对当前this.Instrumentation属性进行初始化,而初始化的代码如下:this.Instrumentation=getInstrumentation(),也就是说在InstrumentationLoadTimeWeaver实例化后其属性Instrumentation已经被初始化为代表当前虚拟机的实例了,在Spring中的bean之间的关系如下:

  1. AspectJWeavingEnabler类中的bean中的loadTimeWeaver属性被初始化为DefaultContextLaodTimeWeaver类型的bean
  2. DefaultContextLoadTimeWeaver类型的bean中的loadTimeWeaver属性被初始化为InstrumentationLoadTimeWeaver

因为AspectJWeavingEnabler类同样实现了BeanFactoryPostProcessor,所以当所有bean解析结束后会调用postPorcessorBeanFactory方法。

public static void enableAspectJWeaving(){
	// DefaultContextLoadTimeWeaver
	// 使用DefaultContextLoadTimeWeaver类型的bean的beanTimeWeaver属性注册转化器
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值