加载时织入(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 transformerClassFileTransformer 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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.AspectJWeavingEnablerweavingEnablerDef.setBeanClassName("org.springframework.context.weaving.AspectJWeavingEnabler");//注册 beanDefinitionparserContext.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 配置文件,如果没有,则不开启 AspectJClassLoader 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 AspectJWeavingEnablerimplements BeanFactoryPostProcessor, BeanClassLoaderAware, LoadTimeWeaverAware, Ordered {private ClassLoader beanClassLoader;private LoadTimeWeaver loadTimeWeaver;public static final String ASPECTJ_AOP_XML_RESOURCE = "META-INF/aop.xml";@Overridepublic void setBeanClassLoader(ClassLoader classLoader) {this.beanClassLoader = classLoader;}@Overridepublic void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {this.loadTimeWeaver = loadTimeWeaver;}@Overridepublic int getOrder() {return HIGHEST_PRECEDENCE;}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {//此时的loadTimeWeaver 为DefaultContextTimeWeaverenableAspectJWeaving(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 类型为AspectJClassBypassingClassFileTransformerweaverToUse.addTransformer(new AspectJClassBypassingClassFileTransformer(new ClassPreProcessorAgentAdapter()));}private static class AspectJClassBypassingClassFileTransformer implements ClassFileTransformer {private final ClassFileTransformer delegate;//设置delegate为ClassPreProcessorAgentAdapterpublic AspectJClassBypassingClassFileTransformer(ClassFileTransformer delegate) {this.delegate = delegate;}@Overridepublic 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-weaver2)getNamespaceURI():获取load-time-weaver命名空间http://www.springframework.org/schema/context3)resolve():获取load-time-weaver对应的 handler , ContextNamespaceHandler1)init(): 初始化load-time-weaver对应的解析器LoadTimeWeaverBeanDefinitionParser1)registerBeanDefinitionParser():注册解析器4)parse():调用 handler 解析标签1)findParserForElement():根据load-time-weaver元素获取解析器LoadTimeWeaverBeanDefinitionParser2)parse():解析元素1)parseInternal():内部解析1)getBeanClassName():获取LoadTimeWeaverBeanDefinitionParser类中的getBeanClassName()方法,获取到默认实现org.springframework.context.weaving.DefaultContextLoadTimeWeaver2)registerBeanDefinition():注册org.springframework.context.weaving.DefaultContextLoadTimeWeaver的 BeanDefinition。2)doParse():1)registerWithGeneratedName():注册org.springframework.context.weaving.AspectJWeavingEnabler 的BeanDefinition2)isBeanConfigurerAspectEnabled():当前 bean 环境中是否有org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect类1) parse():如果有1)setBeanClassName():设置 bean 的名称为org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect2)setFactoryMethodName():设置工厂方法aspectOf3)registerBeanComponent():注册组件名字为org.springframework.context.config.internalBeanConfigurerAspect,值为AnnotationBeanConfigurerAspect 的 BeanDefinition
2)refresh():刷新方法1)prepareBeanFactory(): 准备 bean 工厂1)addBeanPostProcessor(): 创建LoadTimeWeaverAwareProcessor处理器2)invokeBeanFactoryPostProcessors():调用所有的注册的beanFactoryPostProcessor的bean1)getBean(AspectJWeavingEnabler):获取AspectJWeavingEnabler的 bean1)doGetBean():真正获取AspectJWeavingEnabler的 bean1)getSingleton():获取单例 AspectJWeavingEnabler bean1)getObject():单例工厂singletonFactory获取AspectJWeavingEnabler单例 bean1)doCreateBean():创建AspectJWeavingEnabler的 bean1)initializeBean():初始化AspectJWeavingEnabler bean1)applyBeanPostProcessorsBeforeInitialization():初始化AspectJWeavingEnabler之前操作1)postProcessBeforeInitialization(): AspectJWeavingEnabler 的bean 初始化之前调用此方法,LoadTimeWeaverAwareProcessor初始化loadTimeWeaver为DefaultContextLoadTimeWeaver1)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为DefaultContextLoadTimeWeaver2)postProcessBeanFactory(): 处理实现了BeanFactoryPostProcessor的AspectJWeavingEnabler1)enableAspectJWeaving(): 编织切面1)addTransformer(): 调用DefaultContextLoadTimeWeaver.addTransformer(AspectJClassBypassingClassFileTransformer)1)addTransformer():调用InstrumentationLoadTimeWeaver.addTransformer(AspectJClassBypassingClassFileTransformer)方法1)new FilteringClassFileTransformer(transformer):初始化FilteringClassFileTransformer对象,设置targetTransformer为AspectJClassBypassingClassFileTransformer2)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

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

  1. Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理实现(八)

    继上一篇博客,我们继续来分析下面示例的 Spring 静态代理源码实现. 静态 AOP使用示例 加载时织入(Load -Time WEaving,LTW) 指的是在虚拟机载入字节码时动态织入 Aspe ...

  2. Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)

    我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...

  3. Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(三)-Controller 解析

    在之前的博客中Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一),己经对 Spring MVC 的框架做了详细的分析,但是有一个问题,发现举的例子不常用,因为我们在实际开发项 ...

  4. Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)

    在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...

  5. Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean定义(一)

    我们在之前的博客 Spring源码深度解析(郝佳)-学习-ASM 类字节码解析 简单的对字节码结构进行了分析,今天我们站在前面的基础上对Spring中类注解的读取,并创建BeanDefinition做 ...

  6. Spring源码深度解析(郝佳)-学习-源码解析-Spring整合MyBatis

    了解了MyBatis的单独使用过程之后,我们再来看看它也Spring整合的使用方式,比对之前的示例来找出Spring究竟为我们做了什么操作,哪些操作简化了程序开发. 准备spring71.xml &l ...

  7. Spring源码深度解析(郝佳)-学习-源码解析-factory-method

    本文要解析的是Spring factory-method是如何来实现的,话不多说,示例先上来. Stu.java public class Stu {public String stuId;publi ...

  8. Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一)

    Spring框架提供了构建Web应用程序的全部功能MVC模块,通过策略接口,Spring框架是高度可配置的,而且支持多种视图技术,例如JavaServer Pages(JSP),Velocity,Ti ...

  9. Spring源码深度解析(郝佳)-学习-构造器注入

    本文主要是Spring源码有一定基础的小伙伴而言的,因为这里我只想讲一下,Spring对于构造器的注入参数是如何解析,不同参数个数构造器. 相同参数个数,不同参数类型. Spring是如何选择的. 1 ...

最新文章

  1. linux管理磁盘和文件系统
  2. 修改tomcat的临时文件夹_tomcat 临时文件夹被移除的问题
  3. 计算机系统结构教程卷子,计算机系统结构试卷试题.docx
  4. vs2008中combox用法总结
  5. 想一个颠覆性技术方向建议,你能想到什么?
  6. 内核电源管理器已启动关机转换_电气器件-菲尼克斯UPS(不间断电源)使用
  7. 怎样在spyder中暂停程序的运行
  8. poj 1961 KMP的应用
  9. CoolFire系列讲座 第5讲:善用你所得到的任何资讯 (Exm: HOSTS 档)
  10. java 高效的 httpclient_使用httpclient下载zip的有效方法
  11. 代挂管家易开源7.4+web版
  12. 【定位】纯激光导航定位丢失/漂移问题的优化方案及思考
  13. JS基础-模拟京东快递单号查询案例
  14. 动态规划-最短路径问题
  15. Vue指令概述,v-if与v-show的区别
  16. 透明图片怎么发给别人_新手微商没生意咋办?微商怎么做如何推广?不放弃微信就是等死!...
  17. 努比亚 系统升服务器设置,努比亚Nubia x6官方系统升级教程与方法
  18. Flask 学习-9. 开启调试模式(debug模式)的2种方法
  19. 机器视觉——相机选型
  20. 商业分析 —— 有赞零售

热门文章

  1. gm220s路由器怎么设置_子路由器怎么设置?【图文教程】
  2. 交易猫源码搭建+完整版源码
  3. QSqlDatabase QMYSQL driver not loaded
  4. vue-cli打包apk iconfont字体图标 不显示问题
  5. 西门子 S7协议 数据位
  6. 清华大学计算机应用复试题目,2017年清华大学自动化系复试及试题回忆
  7. 基于web的藏汉英在线翻译系统
  8. matlab 2014B ,simulink-simscape 创建 物理 倒立摆-动画-pid 控制 傻瓜教程-100%学会
  9. AutoCAD2018(cad2018)32位/64位中文版
  10. 搜索全部mp3类型文件