Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理(七)

        加载时织入(Load-Time Weaving ,LTW) 指的是在虚拟机加载入字节码文件时动态织入Aspect切面,Spring框架的值添加为 AspectJ LTW在动态织入过程中提供了更细粒度的控制,使用 Java(5+)的代理能使用一个叫"Vanilla" 的 AspectJ LTW,还需要在启动 JVM 的时候将某个 JVM 参数设置打开,这种 JVM范围的设置在一些情况下或者不错,但通常情况下显得有些粗颗粒,而用 Spring在 LTW 能让你在 per-ClassLoader 的基础上打开 LTW ,这显然更加细粒度,并且对“单JVM 多应用” 的环境更具有意义(例如在一个典型的应用服务器环境中),另外,在某些环境下,这能让你使用 LTW 而不对应用服务器的启动脚本做任何改改动,不然,则需要添加 -javaagent:path/to/aspectjweaver.jar 或者(以下将会提及)-javaagent:path/to/Spring-agent.jar,开发人员只需要简单的修改应用上下文的一个或者几个文件就能使用 LTW,而不需要依靠那些管理都部署配置,比如启动脚本的系统管理员。
        这个,我们还是在之前的 AOP 的基础上。继续来写示例吧。

PreGreetingAspect.java
@Aspect
public class PreGreetingAspect {
    @Before("execution(* com.spring_1_100.test_91_100.test97_ltw.*.*(..))")
    public void beforeGreeting() {
        System.out.println("How are you");
    }
}
Waiter.java
public class Waiter{
    public void greetTo(String name){
        System.out.println("xxxxxxxxxxxxxx");
    }
}

Spring xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <context:component-scan base-package="com.spring_1_100.test_91_100.test97_ltw"></context:component-scan>
    <context:load-time-weaver></context:load-time-weaver>
    <bean class="com.spring_1_100.test_91_100.test97_ltw.Waiter"></bean>
</beans>

添加 aop.xml,要在META-INF目标下添加。
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
    <aspects>
        <aspect name="com.spring_1_100.test_91_100.test97_ltw.PreGreetingAspect"></aspect>
    </aspects>

    <weaver options="-showWeaveInfo -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
        <include within="com.spring_1_100.test_91_100.test97_ltw.*"></include>
    </weaver>
    
</aspectj>

添加 VM 参数
在这里插入图片描述
上面要注意的两点

  1. 可能有读者觉得 instrument.jar 包哪里来,只要你配置好 maven 环境后,直接到你的maven 创建目录下寻找即可。
  2. 本次一定要使用 jdk1.7及以下,如果使用 jdk1.8将有莫名其妙的错误。

环境准备好了,来测试一把。

public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring_1_100/config_91_100/spring97_ltw.xml");
    Waiter waiter = ctx.getBean(Waiter.class);
    waiter.greetTo("John");
}

结果输出:
在这里插入图片描述
可能大家还不是很理解,为什么这样就实现了代理,后面,我们再来一一分析源码,在分析源码之前,我们自己来实现一个 instrument.jar 打印出方法执行时间。

        AOP的静态代理主要是在虚拟机启动时改变目标对象字节码的方式来完成对目标对象的增强,它与动态代理相比具有更高的效率,因为在动态代理调用过程中,还需要一个动态创建代理类并代理目标对象的步骤,而静态代理则在启动时便完成对字节码的增强,当系统再次调用目标类时,与调用正常的类并无差别,所以在效率上会相对高一些。
        那我们来看看静态代理是如何使用的吧?Java在1.5引入了 java.lang.instrument,你可以由此实现一个 Java agent,通过此 agent 来修改类的字节码即改变一个类,本节会通过 Java Instrument 实现一个简单的 profiler,当然instrument 并不限于 profiler,instrument 可以做很多的事情,它类似一种低级,更松耦合的 AOP,可以从底层来改变一个类的行为,你可以由此产生无限的遐想,接下来要做的事情,就是计算一个方法所花的时间,通常我们会在代码中按以下方式来编写。

PerfMonAgent
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;

public class PerfMonAgent {
    static private Instrumentation inst = null;
    public static void premain(String agentArgs,Instrumentation _inst){
        System.out.println("PerfMonAgent.premain() was called.");
        //初始化静态变量
        inst = _inst;
        //设置 class-file transformer
        ClassFileTransformer trans = new PerfMonXTransformer();
        System.out.println("Adding a PerMonXformer instance to the JVM 。");
        inst.addTransformer(trans);
    }
}
pom.xml 配置
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.23.1-GA</version>
    <optional>true</optional>
</dependency>

<plugin>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.1.0</version>
    <configuration>
        <archive>
            <manifestEntries>
                <Premain-Class>com.spring.test.PerfMonAgent</Premain-Class>
                <Boot-Class-Path>${project.artifactId}-${project.version}.jar</Boot-Class-Path>
                <Can-Redefine-Classes>false</Can-Redefine-Classes>
                <Can-Retransform-Classes>true</Can-Retransform-Classes>
                <Can-Set-Native-Method-Prefix>false</Can-Set-Native-Method-Prefix>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>
PerfMonXTransformer.java
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
public class PerfMonXTransformer implements ClassFileTransformer {
    @Override
    public final byte[] transform(final ClassLoader loader, final String classFile, final Class<?> classBeingRedefined,
                                  final ProtectionDomain protectionDomain, final byte[] classFileBuffer) {
        if (Utils.isNotNull(classFile)) {
            try {
                final CtClass ctClass = Utils.getCtClass(classFileBuffer, loader);
                for (CtBehavior method : ctClass.getDeclaredBehaviors()) {
                    if (method.getName().contains("$")) {
                        continue;
                    }
                    if ("testMyName".equals(method.getName())) {
                        System.out.println(method.getName());
                        doMethod(method,ctClass);
                    }
                }
                if(classFile.contains("App")){
                    System.out.println("=======================" + classFile);
                    ctClass.writeFile("/Users/quyixiao/git/my-instrument-agent/src/main/java/com/spring/test"); //将上面构造好的类写入到:/Temp中
                }

                return ctClass.toBytecode();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    
    private void doMethod(CtBehavior method ,CtClass ctClass) throws NotFoundException, CannotCompileException {
        method.insertBefore("long time = System.nanoTime();");
        method.insertAfter("System.out.println(System.nanoTime() - time);");
    }
}

        上面两个类就是 agent 的核心了,JVM 启动时在应用加载前会调用 PerfMonAgent.premain(),然后 PerfMonAgent.premain()中实例化了一个定制的 ClassFileTransforme,即 PerfMonXformer 并通过 inst.addTransormer(trans) 把PerfMonXformer的实例加入 Instrumentation 实例(由 JVM传入),这就使得应用中的类加载时,PerfMonXformer.transform都会被调用,你在此方法中可以改变被加载的类,真是有点神奇,能让你很容易的改变类的字节码,在上面的方法中找到我们改变的类的字节码,在每个类的方法入口中加入了 long time=System.nanoTime(),在方法的出口加入了System.out.println(System.nanoTime() - time);
        
        运行 mvn clean install ,安装项目,会在当前项目的 target 下生成my-instrument-agent-1.0-SNAPSHOT.jar包,配置到 idea 的 VM options中。执行 App 方法。

在这里插入图片描述
Java选项中的-javaagent:xx ,其中就是你的 agent JAR ,java 通过此选项来加载 agent,由 agent 来监控 classpath 下的应用。

App.java
public class App {
    public static void main(String[] args) {
        new App().testMyName();
    }
    public void testMyName() {
        System.out.println("Hello World !!");
    }
}

这个项目主要是测试,想执行App 方法 testMyName()所花的时间。因此,我们在方法执行之前和执行之后加上
private void doMethod(CtBehavior method ,CtClass ctClass) throws NotFoundException, CannotCompileException {
        method.insertBefore(“long time = System.nanoTime();”);
        method.insertAfter(“System.out.println(System.nanoTime() - time);”);
}
doMethod()方法,这个方法很简单,方法执行体最开始加上 time 变量,记录当前 na 秒,在方法体执行完成,加上System.out.println(System.nanoTime() - time); 打印出方法执行所耗费的 na秒值。运行
在这里插入图片描述
但是事与愿违,说没有找到变量 time ,在【Spring源码深度解析(郝佳)】这本书上是这样实现了,总是报这个错误,那怎么办呢?分析了一下原因,可能是我没有使用 JBoss 的 javaassit 包吧,在网上寻寻觅觅中,发现CtClass有一个这样的方法writeFile(${path}),将改变的字节码生成到指定的目录中,正如上面代码ctClass.writeFile("/Users/quyixiao/git/my-instrument-agent/src/main/java/com/spring/test"); ,将改变后的字节码文件生成到当前项目中。
在这里插入图片描述
发现字节码变成了下面代码,变量 time 不再是 time 了,变成了 var1 ,因此,上面会抛出没有找到 time 的异常。注意:字节码是在执行时才生成的,上面执行报错,无法生成字节码,所以,先将method.insertAfter(“System.out.println(System.nanoTime() - time);”);改成method.insertAfter(“System.out.println(System.nanoTime()”); 才能生成字节码。

App.class
public class App {
    public App() {
    }
    public static void main(String[] args) {
        (new App()).testMyName();
    }
    public void testMyName() {
        long var1 = System.nanoTime();
        System.out.println("Hello World !!");
        Object var4 = null;
        System.out.println(System.nanoTime());
    }
}

此路不能,那怎么办呢?在网上寻寻觅觅,发现CtBehavior有一个 setBody()方法,那我们再来试试。将 doMethod()改成如下:

private void doMethod(CtBehavior method ,CtClass ctClass) throws NotFoundException, CannotCompileException {
    StringBuffer body = new StringBuffer();
    body.append("{\n long time = System.nanoTime();\n");
    body.append("System.out.println(\"Call to method  took \" + \n (System.nanoTime()-time) + " +  "\" ms.\");\n");
    body.append("}");
    method.setBody(body.toString());
    //method.insertBefore("long time = System.nanoTime();");
    //method.insertAfter("System.out.println(System.nanoTime());");
}

在这里插入图片描述

奇迹出现了,代码没有报错。再来看看字节码。发现 time 变量己经被替换成 var1 ,代码没有报错,打印出时间,但是发现,并没有执行方法体,打印出 Hello World !! ,那怎么办呢?
在这里插入图片描述
那继续来改进代码,思路,先生成一个中间方法,将中间方法再调用目标方法,在方法执行前和方法执行后加上时间计算代码。我们继续来替换 doMethod()方法。

private static void doMethod(CtClass clazz, CtBehavior ctBehavior) throws NotFoundException, CannotCompileException {
	String method = ctBehavior.getName();
    //获取方法信息,如果方法不存在,则抛出异常
    CtMethod ctMethod = clazz.getDeclaredMethod(method);
    //将旧的方法名称进行重新命名,并生成一个方法的副本,该副本方法采用了过滤器的方式
    String nname = method + "$impl";
    ctMethod.setName(nname);
    CtMethod newCtMethod = CtNewMethod.copy(ctMethod, method, clazz, null);

    /*
     * 为该方法添加时间过滤器,用来计算时间。
     * 这就需要我们去判断获取时间的方法是否具有返回值
     */
    String type = ctMethod.getReturnType().getName();
    StringBuffer body = new StringBuffer();
    body.append("{\n long start = System.nanoTime();\n");

    if(!"void".equals(type)) {
        body.append(type + " result = ");
    }

    //可以通过$$将传递给拦截器的参数,传递给原来的方法
    body.append(nname + "($$);\n");

    body.append("System.out.println(\"Call to method " + nname + " took \" + \n (System.nanoTime()-start) + " +  "\" ms.\");\n");
    if(!"void".equals(type)) {
        body.append("return result;\n");
    }
    body.append("}");
    //替换拦截器方法的主体内容,并将该方法添加到class之中
    newCtMethod.setBody(body.toString());
    clazz.addMethod(newCtMethod);

    //输出拦截器的代码块
    System.out.println("拦截器方法的主体:");
    System.out.println(body.toString());
}

这个方法的主要目的是考备原来的方法testMyName()重命名为testMyName$impl(),创建新方法和原方法名字一样,在新方法中调用原来方法。并且方法前后记录调用时间。我们来看看字节码。

public class App {
    public App() {
    }

    public static void main(String[] args) {
        (new App()).testMyName();
    }

    public void testMyName$impl() {
        System.out.println("Hello World !!");
    }

    public void testMyName() {
        long var1 = System.nanoTime();
       	this.testMyName$impl();
        System.out.println("Call to method testMyName$impl took " + (System.nanoTime() - var1) + " ms.");
    }
}

结果毫无疑问,是我们预期的那样,打印出 Hello world!! 和执行时间。
在这里插入图片描述

最终完整版代码

PerfMonXTransformer.java
public class PerfMonXTransformer implements ClassFileTransformer {
    @Override
    public final byte[] transform(final ClassLoader loader, final String classFile, final Class<?> classBeingRedefined,
                                  final ProtectionDomain protectionDomain, final byte[] classFileBuffer) {
        if (Utils.isNotNull(classFile)) {
            try {
                final CtClass ctClass = Utils.getCtClass(classFileBuffer, loader);
                for (CtBehavior method : ctClass.getDeclaredBehaviors()) {
                    if (method.getName().contains("$")) {
                        continue;
                    }
                    if ("testMyName".equals(method.getName())) {
                        System.out.println(method.getName());
                        doMethod(ctClass,method);
                    }
                }
                if(classFile.contains("App")){
                    System.out.println("=======================" + classFile);
                    ctClass.writeFile("/Users/quyixiao/git/my-instrument-agent/src/main/java/com/spring/test"); //将上面构造好的类写入到:/Temp中
                }

                return ctClass.toBytecode();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private static void doMethod(CtClass clazz, CtBehavior ctBehavior) throws NotFoundException, CannotCompileException {
        String method = ctBehavior.getName();
        //获取方法信息,如果方法不存在,则抛出异常
        CtMethod ctMethod = clazz.getDeclaredMethod(method);
        //将旧的方法名称进行重新命名,并生成一个方法的副本,该副本方法采用了过滤器的方式
        String nname = method + "$impl";
        ctMethod.setName(nname);
        CtMethod newCtMethod = CtNewMethod.copy(ctMethod, method, clazz, null);

        /*
         * 为该方法添加时间过滤器,用来计算时间。
         * 这就需要我们去判断获取时间的方法是否具有返回值
         */
        String type = ctMethod.getReturnType().getName();
        StringBuffer body = new StringBuffer();
        body.append("{\n long start = System.nanoTime();\n");

        if(!"void".equals(type)) {
            body.append(type + " result = ");
        }

        //可以通过$$将传递给拦截器的参数,传递给原来的方法
        body.append(nname + "($$);\n");

        body.append("System.out.println(\"Call to method " + nname + " took \" + \n (System.nanoTime()-start) + " +  "\" ms.\");\n");
        if(!"void".equals(type)) {
            body.append("return result;\n");
        }

        body.append("}");

        //替换拦截器方法的主体内容,并将该方法添加到class之中
        newCtMethod.setBody(body.toString());
        clazz.addMethod(newCtMethod);

        //输出拦截器的代码块
        System.out.println("拦截器方法的主体:");
        System.out.println(body.toString());
    }

    private void doMethodBak(CtClass ctClass,CtBehavior method ) throws NotFoundException, CannotCompileException {
        StringBuffer body = new StringBuffer();
        body.append("{\n long time = System.nanoTime();\n");
        body.append("System.out.println(\"Call to method  took \" + \n (System.nanoTime()-time) + " +  "\" ms.\");\n");
        body.append("}");
        method.setBody(body.toString());
        //method.insertBefore("long time = System.nanoTime();");
        //method.insertAfter("System.out.println(System.nanoTime());");
    }
}

本项目的 github 地址是https://github.com/quyixiao/my-instrument-agent.git
        通过这个项目,我们基本理解了,Spring 静态代理,在项目启动前,修改类字节码,来实现方法的横切面的。由执行结果来看,执行顺序以及通过改变 App 的字节码加入监控代码确实生效了,你也可以发现,通过 Instrument 实现 agent 使得监控代码和应用代码完全隔离了。
        通过之前的这两个小示例我们似乎己经有所体会,在 Spring 中静态 AOP 直接使用 AspectJ 提供的方法,而 AspectJ 又是在 instrument 基础上进行封装的,就以上面的例子来看,至少 AspectJ 中会有如下功能。

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

        当然,这都是 AspectJ 所做的事情,并不是我们讨论的范畴,Spring 直接使用 AspectJ,也就是动态代理的任务直接委托给了 AspectJ,那么 Spring 怎样嵌入 AspectJ 的呢?同样我们还是从配置入手。

ContextNamespaceHandler.java
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
}

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

LoadTimeWeaverBeanDefinitionParser.java
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
    builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    if (isAspectJWeavingEnabled(element.getAttribute("aspectj-weaving"), parserContext)) {
        RootBeanDefinition weavingEnablerDef = new RootBeanDefinition();
        //设置 beanName 为org.springframework.context.weaving.AspectJWeavingEnabler
        weavingEnablerDef.setBeanClassName("org.springframework.context.weaving.AspectJWeavingEnabler");
        //注册 beanDefinition
        parserContext.getReaderContext().registerWithGeneratedName(weavingEnablerDef);
        //当前beanClassLoader是否加载了org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect类
        if (isBeanConfigurerAspectEnabled(parserContext.getReaderContext().getBeanClassLoader())) {
        	//使用SpringConfiguredBeanDefinitionParser解析元素
            new SpringConfiguredBeanDefinitionParser().parse(element, parserContext);
        }
    }
}

        其实之前分析动态 AOP 也就是在分析配置<aop:aspectj-authproxy/> 中己经提到了自定义配置解析过程,对于<aop:aspectj-autoproxy/>的解析无非是以标签作为标志,进而进行相关处理类的注册,那么对于自定义标签<context:load-time-weaver/>其实起到了同样的作用。
        上面函数的核心作用其实就是注册一个对于 ApectJ 处理类 org.springframework.context.weaving.AspectJWeavingEnabler,它的注册流程总结起来如下。

  1. 是否开启了 AspectJ
  2. 之前虽然反复提到了在配置文件中加入了<context:load-time-weaver/>便相当于加入了 AspectJ 开头,但是并不是配置了这个标签就意味着开启了 AspectJ 的功能,这个标签中还有一个属性 aspect-weaving,这个属性有3个备选值,on,off和 autodetect,默认为 autodectect,也就是说,如果我们使用了<context:load-time-weaver/>,那么 Spring 会帮助我们检测是否可以使用 AspectJ 的功能,而检测的依据便是 META-INF/aop.xml 是否存在,看看 Spring 中代码的实现方式。
protected boolean isAspectJWeavingEnabled(String value, ParserContext parserContext) {
    if ("on".equals(value)) {
        return true;
    }
    else if ("off".equals(value)) {
        return false;
    }
    else {
    	//在META-INF是否在 aop.xml 配置文件,如果没有,则不开启 AspectJ
        ClassLoader cl = parserContext.getReaderContext().getResourceLoader().getClassLoader();
        return (cl.getResource("META-INF/aop.xml") != null);
    }
}

        2.将 org.springframework.context.weaving.AspectJWeavingEnabler 封装在 BeanDefinition 中注册。
        当通过 AspectJ 功能验证后便可以进行 AspectJWeavingEnabler 的注册了,注册方式很简单,无非是将类路径在新初始化的 RootBeanDefinition 中,在 RootBeanDefinition 的获取时会转换成对应的 class。
        尽管在 init方法中注册了 AspectJWeavingEnabler,但是对于标签本身 Spring 也会以 bean的形式保存,也就是当 Spring 解析到<context:load-time-weaver/>标签的时候会产生一个bean,而这个 bean 中的信息是什么呢?

public BeanDefinition parse(Element element, ParserContext parserContext) {
	//构建org.springframework.context.config.internalBeanConfigurerAspect的 BeanDefinition
	//设置其工厂方法为aspectOf()方法
	if (!parserContext.getRegistry().containsBeanDefinition("org.springframework.context.config.internalBeanConfigurerAspect")) {
		RootBeanDefinition def = new RootBeanDefinition();
		def.setBeanClassName("org.springframework.context.config.internalBeanConfigurerAspect");
		def.setFactoryMethodName("aspectOf");
		def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		def.setSource(parserContext.extractSource(element));
		parserContext.registerBeanComponent(new BeanComponentDefinition(def, BEAN_CONFIGURER_ASPECT_BEAN_NAME));
	}
	return null;
}

在LoadTimeWeaverBeanDefinitionParser类中有一个这样的函数 :

protected String getBeanClassName(Element element) {
	if (element.hasAttribute("weaver-class")) {
		return element.getAttribute("weaver-class");
	}
	return "org.springframework.context.weaving.DefaultContextLoadTimeWeaver";
}

        单凭以上的信息我们至少可以推断,当 Spring在读取到自定义标签<context:load-time-weaver/>会产生一个 bean,而这个 bean 的 id 为 loadTimeWeaver,class 为 org.springframework.context.weaving.DefaultContextLoadTimeWeaver,也就是完成了 DefaultContextLoadTimeWeaver 类的注册。
        完成了以上的注册功能后,并不意味这在 Spring 中就可以使用了AspectJ了,因为我们还有一个很重要的步骤,就是 LoadTimeWeaverAwareProcessor 的注册,在 AbstractApplicationContext 中的 prepareBeanFactory 函数中有一段这样的代码:

if (beanFactory.containsBean("loadTimeWeaver")) {
	beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
	beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}

        在 AbstractApplicationContext中的 prepareBeanFactory 函数在容器初始化的时候调用的,也就是说只有在注册了 LoadTimeWeaverAwareProcessor 才会激活 AspectJ 的功能。
        LoadTimeWeaverAwareProcessor实现 BeanPostProcessor 方法,那么对于 BeanProcessor 接口来讲,postProcessBeforeInitialization 与 postProcessAfterrrrrInitialization 有着特殊的意思,也就是说,在所有的 bean 的初始化之前与之后都会分别调用对应的方法,那么 LoadTimeWeaverAwareProcessor 中的 postProcessBeforeInitialization函数中完成了什么样的逻辑呢?

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
	if (bean instanceof LoadTimeWeaverAware) {
		LoadTimeWeaver ltw = this.loadTimeWeaver;
		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 判断注定这个后处理器只对 LoadTimeWeaver接口的类只有 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销毁时调用 destory 方法进行 bean 的清理,而 BeanClassLoaderAware 接口则保证在 bean初始化调用 AbstractAutowireCapableBeanFactory 的 invokeAwareMethods 时调用 setBeanClassLoader 方法。

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");
		}
	}
}

        上面的函数中有一句很容易被忽略但是很关键的代码:
        this.loadTimeWeaver = new InstrumentationLoadTimeWeaver(classLoader);
        这句代码不仅是实例化了一个 InstrumentationLoadTimeWeaver类型的实例,而且在实例化的过程中还做了一些额外的操作。
        在实例化的过程中会对当前的 this.instrumentation 属性进行初始化,而初始化代码如下:this.instrumentaction=getInstrumentation(),也就是实例化后,其属性 Instrumentation 己经初始化为代表着当前虚拟机的实例了,综合我们讲过的例子,对于注册转换器,如 addTransformer 函数等,便可以直接使用此属性进行操作了。
        也就是经过以上的程序处理后,在 Spring 与 Bean之间的关系如下:
        1.AspectJWeavingEnabler 类型的 bean 中的 loadTimeWeaver 属性被初始化 DefaultContextTimeWeaver 类型的 bean 。
        2.DefaultContextTimeWeaver 类型的 bean 中的 loadTimeWeaver 属性被初始化为 InstrumentationLoadTimeWeaver。
        因为 AspectJWeavingEnabler 类同样实现了 BeanFactoryPostProcessor,所以当所有的 bean 解析结束后会调用其 postProcessBeanFactory 方法。

AspectJWeavingEnabler
public class AspectJWeavingEnabler
		implements BeanFactoryPostProcessor, BeanClassLoaderAware, LoadTimeWeaverAware, Ordered {

	private ClassLoader beanClassLoader;

	private LoadTimeWeaver loadTimeWeaver;

	public static final String ASPECTJ_AOP_XML_RESOURCE = "META-INF/aop.xml";


	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.beanClassLoader = classLoader;
	}

	@Override
	public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
		this.loadTimeWeaver = loadTimeWeaver;
	}

	@Override
	public int getOrder() {
		return HIGHEST_PRECEDENCE;
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		//此时的loadTimeWeaver 为DefaultContextTimeWeaver
		enableAspectJWeaving(this.loadTimeWeaver, this.beanClassLoader);
	}

	public static void enableAspectJWeaving(LoadTimeWeaver weaverToUse, ClassLoader beanClassLoader) {
		if (weaverToUse == null) {
			if (InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
				weaverToUse = new InstrumentationLoadTimeWeaver(beanClassLoader);
			}
			else {
				throw new IllegalStateException("No LoadTimeWeaver available");
			}
		}
		//使用DefaultContextTimeWeaver 设置 loadTimeWeaver 类型为AspectJClassBypassingClassFileTransformer
		weaverToUse.addTransformer(new AspectJClassBypassingClassFileTransformer(
					new ClassPreProcessorAgentAdapter()));
	}


	private static class AspectJClassBypassingClassFileTransformer implements ClassFileTransformer {

		private final ClassFileTransformer delegate;
		//设置delegate为ClassPreProcessorAgentAdapter
		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;
			}
			return this.delegate.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
		}
	}
}

        transform()对所有的类进行字节码转换处理。

过程:

1)parseCustomElement(): 解析自定义标签load-time-weaver
	2)getNamespaceURI():获取load-time-weaver命名空间http://www.springframework.org/schema/context
	3)resolve():获取load-time-weaver对应的 handler , ContextNamespaceHandler
		1)init(): 初始化load-time-weaver对应的解析器LoadTimeWeaverBeanDefinitionParser
			1)registerBeanDefinitionParser():注册解析器
	4)parse():调用 handler 解析标签
		1)findParserForElement():根据load-time-weaver元素获取解析器LoadTimeWeaverBeanDefinitionParser
		2)parse():解析元素
			1)parseInternal():内部解析
				1)getBeanClassName():获取LoadTimeWeaverBeanDefinitionParser类中的getBeanClassName()方法,获取到默认实现org.springframework.context.weaving.DefaultContextLoadTimeWeaver
				2)registerBeanDefinition():注册org.springframework.context.weaving.DefaultContextLoadTimeWeaver的 BeanDefinition。
			2)doParse():
				1)registerWithGeneratedName():注册org.springframework.context.weaving.AspectJWeavingEnabler 的BeanDefinition
				2)isBeanConfigurerAspectEnabled():当前 bean 环境中是否有org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect类
					1) parse():如果有
						1)setBeanClassName():设置 bean 的名称为org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect
						2)setFactoryMethodName():设置工厂方法aspectOf
						3)registerBeanComponent():注册组件名字为org.springframework.context.config.internalBeanConfigurerAspect,值为AnnotationBeanConfigurerAspect 的 BeanDefinition
2)refresh():刷新方法
	1)prepareBeanFactory(): 准备 bean 工厂
		1)addBeanPostProcessor(): 创建LoadTimeWeaverAwareProcessor处理器
	2)invokeBeanFactoryPostProcessors():调用所有的注册的beanFactoryPostProcessor的bean
		1)getBean(AspectJWeavingEnabler):获取AspectJWeavingEnabler的 bean
			1)doGetBean():真正获取AspectJWeavingEnabler的 bean
				1)getSingleton():获取单例 AspectJWeavingEnabler bean
					1)getObject():单例工厂singletonFactory获取AspectJWeavingEnabler单例 bean
						1)doCreateBean():创建AspectJWeavingEnabler的 bean
							1)initializeBean():初始化AspectJWeavingEnabler bean
								1)applyBeanPostProcessorsBeforeInitialization():初始化AspectJWeavingEnabler之前操作
									1)postProcessBeforeInitialization(): AspectJWeavingEnabler 的bean 初始化之前调用此方法,LoadTimeWeaverAwareProcessor初始化loadTimeWeaver为DefaultContextLoadTimeWeaver
										1)getBean("loadTimeWeaver"): 注册DefaultContextLoadTimeWeaver到容器中
											1)invokeAwareMethods(): bean 初始化调用此方法
												1)setBeanClassLoader(): 设置 bean 类加载器.
													1)isInstrumentationAvailable():当前环境中是否有instrumentation对象,当前是否调用了InstrumentationSavingAgent中的premain()或者agentmain()方法,这个方法,只要我们 VM options 中配置了-javaagent:/path/to/4.2.1.RELEASE/spring-instrument-4.2.1.RELEASE.jar,即会调用
													2) new InstrumentationLoadTimeWeaver(classLoader):创建InstrumentationLoadTimeWeaver对象
														1)getInstrumentation():获取InstrumentationSavingAgent类premain()或者agentmain()方法传入的 inst,也就是虚拟机当前实例
										2)setLoadTimeWeaver():设置AspectJWeavingEnabler的loadTimeWeaver为DefaultContextLoadTimeWeaver
		2)postProcessBeanFactory(): 处理实现了BeanFactoryPostProcessor的AspectJWeavingEnabler
			1)enableAspectJWeaving(): 编织切面
				1)addTransformer(): 调用DefaultContextLoadTimeWeaver.addTransformer(AspectJClassBypassingClassFileTransformer)
					1)addTransformer():调用InstrumentationLoadTimeWeaver.addTransformer(AspectJClassBypassingClassFileTransformer)方法
						1)new FilteringClassFileTransformer(transformer):初始化FilteringClassFileTransformer对象,设置targetTransformer为AspectJClassBypassingClassFileTransformer
						2)addTransformer():instrumentation.addTransformer(FilteringClassFileTransformer)方法,将虚拟机实例中加入AspectJClassBypassingClassFileTransformer对象。虚拟机将会调用transform方法,修改类的字节码值。

        从之前的例子来看,静态代理,是在调用premain()方法或者agentmain()修改字节码结构,从而实现类方法的改动,但是今天我们看 Spring 源码中,Spring 并没有直接在 main 方法启动前就修改了类字节码,而是将虚拟机对象保存起来,在 bean 实例化之前,才对每一个 bean 进行切面匹配,匹配成功,再修改类字节码。切面是如何匹配,字节码是怎样修改的,我们下一篇博客再来分析。

本文的 git地址是
https://github.com/quyixiao/spring_tiny/tree/master/src/main/java/com/spring_1_100/test_91_100/test97_ltw

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值