当我们完成了所有的AspectJ的准备工作后便可以进行织入分析了,首先还是从LoadTimeWeaverAwareProcessor开始。

LoadTimeWeaverAwareProcessor实现BeanPostProcessor方法,那么对于BeanPostProcessor接口来讲,postProcessBeforeInitialization与postProcessAfterInitialization有着其特殊意义,也就是说在所有bean的初始化之前与之后都会分别调用对应的方法,那么在LoadTimeWeaverAwareProcessor中的postProcessBeforeInitialization函数中完成了什么样的逻辑呢?

    @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof LoadTimeWeaverAware) {
LoadTimeWeaver ltw = this.loadTimeWeaver;//DefaultContextLoadTimeWeaver
if (ltw == null) {
Assert.state(this.beanFactory != null,
"BeanFactory required if no LoadTimeWeaver explicitly specified");
ltw = this.beanFactory.getBean(
ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME, LoadTimeWeaver.class);
}
((LoadTimeWeaverAware) bean).setLoadTimeWeaver(ltw);
}
return bean;
}

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

当在Spring中调用AspectJWeavingEnabler时,this.loadTimeWeaver尚未被初始化,那么,会直接调用beanFactory.getBean方法获取对应的DefaultContextLoadTimeWeaver类型的bean,并将其设置为AspectJWeavingEnabler类型bean的loadTimeWeaver属性中。

AspectJWeavingEnabler实现了BeanClassLoaderAware以及Ordered接口,实现BeanClassLoaderAware接口保证了在bean初始化的时候调用AbstractAutowireCapableBeanFactory的invokeAwareMethods的时候将beanClassLoader赋值给当前类。而实现Ordered接口则保证在实例化bean时当前bean会被最先初始化。

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

    @Override
public void setBeanClassLoader(ClassLoader classLoader) {
LoadTimeWeaver serverSpecificLoadTimeWeaver = createServerSpecificLoadTimeWeaver(classLoader);
if (serverSpecificLoadTimeWeaver != null) {
if (logger.isInfoEnabled()) {
logger.info("Determined server-specific load-time weaver: " +
serverSpecificLoadTimeWeaver.getClass().getName());
}
this.loadTimeWeaver = serverSpecificLoadTimeWeaver;
}
else if (InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
       //检查当前虚拟机中的Instrumentation实例是否可用
logger.info("Found Spring's JVM agent for instrumentation");
this.loadTimeWeaver = new InstrumentationLoadTimeWeaver(classLoader);
}
else {
try {
this.loadTimeWeaver = new ReflectiveLoadTimeWeaver(classLoader);
logger.info("Using a reflective load-time weaver for class loader: " +
this.loadTimeWeaver.getInstrumentableClassLoader().getClass().getName());
}
catch (IllegalStateException ex) {
throw new IllegalStateException(ex.getMessage() + " Specify a custom LoadTimeWeaver or start your " +
"Java virtual machine with Spring's agent: -javaagent:org.springframework.instrument.jar");
}
}
}

也就是经过以上程序setBeanClassLoader和postProcessBeforeInitialization的处理后,在Spring中的bean之间的关系如下:

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

上面的函数中有一句很容易被忽略但是很关键的代码:

this.loadTimeWeaver = new InstrumentationLoadTimeWeaver(classLoader);

这句代码不仅仅是实例化了一个InstrumentationLoadTimeWeaver类型的实例,而且在实例化过程中还做了一些额外的操作。在实例化过程中判断了当前是否存在Instrumentation实例,最终会取InstrumentationSavingAgent类中的instrumentation的静态属性,判断这个属性是否是null,InstrumentationSavingAgent这个类是spring-instrument-3.2.9.RELEASE.jar的代理入口类,当应用程序启动时启动了spring-instrument-3.2.9.RELEASE.jar代理时,即在虚拟机参数中设置了-javaagent参数,虚拟机会创建Instrumentation实例并传递给premain方法,InstrumentationSavingAgent会把这个类保存在instrumentation静态属性中。所以在程序启动时启动了代理时InstrumentationLoadTimeWeaver.isInstrumentationAvailable()这个方法是返回true的,所以loadTimeWeaver属性会设置成InstrumentationLoadTimeWeaver对象。对于注册转换器,如addTransformer函数等,便可以直接使用此属性(instrumentation)进行操作了。

public class InstrumentationSavingAgent {
private static volatile Instrumentation instrumentation;
public static void premain(String agentArgs, Instrumentation inst) {
instrumentation = inst;
}
public static Instrumentation getInstrumentation() {
return instrumentation;
} }

因为AspectJWeavingEnabler类同样实现了BeanFactoryPostProcessor,所以当所有bean解析结束后会调用其postProcessBeanFactory方法。看下AspectJWeavingEnabler类的enableAspectJWeaving方法,

    @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
enableAspectJWeaving(this.loadTimeWeaver, this.beanClassLoader);
}
public static void enableAspectJWeaving(LoadTimeWeaver weaverToUse, ClassLoader beanClassLoader) {
if (weaverToUse == null) {
       //此时已经被初始化为DefaultContextLoadTimeWeaver
if (InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
weaverToUse = new InstrumentationLoadTimeWeaver(beanClassLoader);
}
else {
throw new IllegalStateException("No LoadTimeWeaver available");
}
}
     //使用DefaultContextLoadTimeWeaver类型的bean中的loadTimeWeaver属性注册转换器
weaverToUse.addTransformer(new AspectJClassBypassingClassFileTransformer(
new ClassPreProcessorAgentAdapter()));
}

AspectJClassBypassingClassFileTransformer类和ClassPreProcessorAgentAdapter类都实现了字节码转换接口ClassFileTransformer

    private static class AspectJClassBypassingClassFileTransformer implements ClassFileTransformer {
private final ClassFileTransformer delegate;
public AspectJClassBypassingClassFileTransformer(ClassFileTransformer delegate) {
this.delegate = delegate;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (className.startsWith("org.aspectj") || className.startsWith("org/aspectj")) {
return classfileBuffer;
}
       //委托给AspectJ代理继续处理
return this.delegate.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
}
}

这也是一个修饰器模式,最终会调用ClassPreProcessorAgentAdapter的transform方法执行字节码转换逻辑,在类加载器定义类时(即调用defineClass方法)会调用此类的transform方法来进行字节码转换替换原始类。

  1. AspectJClassBypassingClassFileTransformer的作用仅仅是告诉AspectJ以org.aspectj开头的或者org/aspectj开头的类不进行处理。
  2. ClassPreProcessorAgentAdapter类中的代码比较多,它的主要工作是解析aop.xml文件,解析类中的Aspect注解,并且根据解析结果来生成转换后的字节码。

接下来就看看InstrumentationLoadTimeWeaver类的addTransformer方法代码:

public void addTransformer(ClassFileTransformer transformer) {
Assert.notNull(transformer, "Transformer must not be null");
FilteringClassFileTransformer actualTransformer =
new FilteringClassFileTransformer(transformer, this.classLoader);
synchronized (this.transformers) {
if (this.instrumentation == null) {
throw new IllegalStateException(
"Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.");
}
     //加入到jdk的instrumentation中加载class时自动调用
     this.instrumentation.addTransformer(actualTransformer);
this.transformers.add(actualTransformer);
}
}

从代码中可以看到,这个方法中,把类转换器actualTransformer通过instrumentation实例注册给了虚拟机。这里采用了修饰器模式,actualTransformer对transformer进行修改封装,下面是FilteringClassFileTransformer这个内部类的代码:

private static class FilteringClassFileTransformer implements ClassFileTransformer {
private final ClassFileTransformer targetTransformer;
private final ClassLoader targetClassLoader;
public FilteringClassFileTransformer(ClassFileTransformer targetTransformer, ClassLoader targetClassLoader) {
this.targetTransformer = targetTransformer;
this.targetClassLoader = targetClassLoader;
}
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (!this.targetClassLoader.equals(loader)) {
return null;
}
return this.targetTransformer.transform(
loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
}
@Override
public String toString() {
return "FilteringClassFileTransformer for: " + this.targetTransformer.toString();
}
}

这里面的targetClassLoader就是容器的bean类加载,在进行类字节码转换之前先判断执行类加载的加载器是否是bean类加载器,如果不是的话跳过类装换逻辑直接返回null,返回null的意思就是不执行类转换还是使用原始的类字节码。什么情况下会有类加载不是bean的类加载器的情况?AbstractApplicationContext的prepareBeanFactory方法中有一行代码:

        // Detect a LoadTimeWeaver and prepare for weaving, if found.
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}

当容器中注册了loadTimeWeaver之后会给容器设置一个ContextTypeMatchClassLoader类型的临时类加载器,在织入切面时只有在bean实例化时织入切面才有意义,在进行一些类型比较或者校验的时候,比如判断一个bean是否是FactoryBean、BPP、BFPP,这时候不涉及到实例化,所以做字节码转换没有任何意义,而且还会增加无谓的性能消耗,所以在进行这些类型比较时使用这个临时的类加载器执行类加载,这样在上面的transform方法就会因为类加载不匹配而跳过字节码转换,这里有一点非常关键的是,ContextTypeMatchClassLoader的父类加载就是容器bean类加载器,所以ContextTypeMatchClassLoader类加载器是不遵循“双亲委派”的,因为如果它遵循了“双亲委派”,那么它的类加载工作还是会委托给bean类加载器,这样的话if里面的条件就不会匹配,还是会执行类转换。ContextTypeMatchClassLoader的类加载工作会委托给ContextOverridingClassLoader类对象,有兴趣可以看看ContextOverridingClassLoader和OverridingClassLoader这两个类的代码。这个临时的类加载器会在容器初始化快结束时,容器bean实例化之前被清掉,代码在AbstractApplicationContext类的finishBeanFactoryInitialization方法:

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
...
beanFactory.setTempClassLoader(null);
// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}

AOP静态代理解析2-代码织入的更多相关文章

  1. AOP动态代理解析2-代码织入入口

    通过自定义配置完成了对AnnotationAwareAspectJAutoProxyCreator类型的自动注册,那么这个类到底做了什么工作来完成AOP的操作呢?首先我们看看AnnotationAwa ...

  2. AOP静态代理解析1-标签解析

    AOP静态代理使用示例见Spring的LoadTimeWeaver(代码织入) Instrumentation使用示例见java.lang.instrument使用 AOP的静态代理主要是在虚拟机启动 ...

  3. 30个类手写Spring核心原理之AOP代码织入(5)

    本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...

  4. .NET静态代码织入——肉夹馍(Rougamo)

    肉夹馍是什么 肉夹馍通过静态代码织入方式实现AOP的组件..NET常用的AOP有Castle DynamicProxy.AspectCore等,以上两种AOP组件都是通过运行时生成一个代理类执行AOP ...

  5. .NET静态代码织入——肉夹馍(Rougamo) 发布1.1.0

    肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...

  6. .NET静态代码织入——肉夹馍(Rougamo) 发布1.2.0

    肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...

  7. 框架源码系列三:手写Spring AOP(AOP分析、AOP概念学习、切面实现、织入实现)

    一.AOP分析 问题1:AOP是什么? Aspect Oriented Programming 面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强. 问题2:我们需要做什么? 在我们的框架中 ...

  8. Spring的LoadTimeWeaver(代码织入)

    在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入.类加载期织入和运行期织入.编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中:而类加载期织入则指通过特 ...

  9. Spring的LoadTimeWeaver(代码织入)(转)

    https://www.cnblogs.com/wade-luffy/p/6073702.html 在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入.类加载期织入和运行期织入. ...

随机推荐

  1. 【动态规划】简单背包问题II

    问题 B: [动态规划]简单背包问题II 时间限制: 1 Sec  内存限制: 64 MB提交: 21  解决: 14[提交][状态][讨论版] 题目描述 张琪曼:“为什么背包一定要完全装满呢?尽可能 ...

  2. Mysql 练习 总结

    insert into Course values ('); #---------------Teacher--添加信息---- insert into Teacher values (','李成', ...

  3. JQuery页面加载

    第一种: $(document).ready(function(){ alert("第一种方法."); }); 第二种: $(function(){ alert("第二种 ...

  4. 【编程题目】在一个字符串中找到第一个只出现一次的字符。如输入 abaccdeff,则输出 b。

    第 17 题(字符串):题目:在一个字符串中找到第一个只出现一次的字符.如输入 abaccdeff,则输出 b. 思路:此题非常容易. 最开始是想开辟一块空间存储每个字符出现的次数. 但转念一想,似乎 ...

  5. [Android Pro] Android打包一个Apk后,如何查看它的VersionCode、VersionName 等等。

    Android打包成Apk后,其实是一个压缩文件,我们用winrar打开也能看到里面的文件结构.还能看到AndroidManifest.但是里面的内容有点问题. 不知道是因为加密还是Android就是 ...

  6. C 工厂模式 还有其他的模式

    http://blog.csdn.net/feixiaoxing/article/details/7081243

  7. 大数运算(python2)

    偶然又遇到了一道大数题,据说python大数运算好屌,试了一发,果然方便-1 a = int( raw_input() ); //注意这里是按行读入的,即每行只读一个数 b = int( raw_in ...

  8. SQL 映射的 XML 文件

    MyBatis 真正的力量是在映射语句中.这里是奇迹发生的地方. 对于所有的力量, SQL映射的 XML 文件是相当的简单.当然如果你将它们和对等功能的 JDBC 代码来比较,你会发现映射文件节省了大 ...

  9. Html和CSS的关系

    1. HTML是网页内容的载体.内容就是网页制作者放在页面上想要让用户浏览的信息,可以包含文字.图片.视频等. 2. CSS样式是表现.就像网页的外衣.比如,标题字体.颜色变化,或为标题加入背景图片. ...

  10. 通过btn获取所在cell

    [cell.btn addTarget:self action:@selector(cellBtnClicked:event:) forControlEvents:UIControlEventTouc ...