前言
本文介绍Spring AOP中Load Time Weaver的初始化过程,对其中有意思的一两个问题做分析和深入讨论,期待对你有所帮助。
一、先聊Load Time
阅读过第九篇的小伙伴,应该了解到class文件的产生和加载有3个大的阶段,编译,加载,运行。其中Load Time就是加载阶段。JDK 5.0之后提供了Instrument API,允许Java Agent注册Class Transformer。当JVM完成原始的class文件加载后,会运行该transformer做进一步的转换。
说到类的加载,日常的项目中有很多类,加载顺序是怎么样的呢?被动加载加主动加载。被动加载可以理解为按需加载,也就是使用时再加载,毕竟这样可以节约内存。主动加载,可以理解为一旦启动必须会用到的那些类,直接加载上得了,这样可以缩短应用初始化时间。
二、再说Weaver
Weaver的作用是将Aspect和具体的JoinPoint关联起来。拿监听者模式来类比,JoinPoint就是一个具体的EventSource,会在必要的时候产生Event,Aspect是对该Event感兴趣的EventListener,Weaver则把EventSource和EventListener关联起来。这里有一个潜在的依赖关系。那EventListener、EventSource两者谁依赖谁呢?
从处理逻辑上,逻辑自EventSource到EventListener,所以是EventListener依赖EventSource。实际的业务场景中,我们可能不希望event有丢失,也就是event肯定会被EventListener处理到,所以我们先初始化好EventListener,然后用EventListener来初始化EventSource。此时剧情又反转了。
总之,这个潜在的依赖关系对最终结果是有影响的。
三、回看Load Time Weaver
Load Time Weaver就是在Load Time将Aspect和JoinPoint Weave起来。玩过@Aspect的都知道,Aspect中声明了一个PointCut,仅站在Java语言层面PointCut的实际内容就是个字符串。假设其要对"org.apache"下的所有类的所有方法做切面通知,该声明不会导致类加载器去加载包“org.apache”下的所有类。这就导致,EventListener ready的时候,找不到具体的EventSource,weave过程失败。
Spring中是这么解决的?a、EventListener在所有的EventSource之前先初始化好,基于JVM agent机制;b、监控当前classLoader后续的加载类,做针对性的transformer处理。
到这里,想必你心中已经建立了关于LTW的导航地图,接下里就按照地图走一波。
四、LTW初始化过程
1、激活
XML方式:
<context:load-time-weaver />
其解析过程通过ContextNamespaceHandler中注册的LoadTimeWeaverBeanDefinitionParser来完成,最终向BeanFactory注册了两个关键类
org.springframework.context.weaving.AspectJWeavingEnabler
org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect
Annotation方式:
@EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
@Configuration
public class App {
}
@EnableLoadTimeWeaving 标签的定义中import了LoadTimeWeaverConfiguration,后者实现了ImportAware对当前上下文的Meta做进一步的处理,其中完成了ltw的初始化
@Bean(name = ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public LoadTimeWeaver loadTimeWeaver() {
Assert.state(this.beanClassLoader != null, "No ClassLoader set");
LoadTimeWeaver loadTimeWeaver = null;
if (this.ltwConfigurer != null) {
// The user has provided a custom LoadTimeWeaver instance
loadTimeWeaver = this.ltwConfigurer.getLoadTimeWeaver();
}
if (loadTimeWeaver == null) {
// No custom LoadTimeWeaver provided -> fall back to the default
loadTimeWeaver = new DefaultContextLoadTimeWeaver(this.beanClassLoader);
}
if (this.enableLTW != null) {
AspectJWeaving aspectJWeaving = this.enableLTW.getEnum("aspectjWeaving");
switch (aspectJWeaving) {
case DISABLED:
// AJ weaving is disabled -> do nothing
break;
case AUTODETECT:
if (this.beanClassLoader.getResource(AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE) == null) {
// No aop.xml present on the classpath -> treat as 'disabled'
break;
}
// aop.xml is present on the classpath -> enable
AspectJWeavingEnabler.enableAspectJWeaving(loadTimeWeaver, this.beanClassLoader);
break;
case ENABLED:
AspectJWeavingEnabler.enableAspectJWeaving(loadTimeWeaver, this.beanClassLoader);
break;
}
}
return loadTimeWeaver;
}
注意这里只是激活,准确说是提议,相当于我们常说的“我想xxx", 具体是否真的能够LTW还要看具体配置。因为Spring AOP的LTW是重用了AspectJ中的LTW能力。因此内部仅做了必要的嫁接,实际的激活还要看基于AspectJ的配置是否到位。
首当其冲的配置自然是Java Agent是否配置,要包含的命令行参数如下:
-javaagent:spring-instrument-5.3.15.jar -javaagent:aspectjweaver-1.9.9.jar
其次是 aop.xml 配置是否存在。注解方式中ENABLED,表示直接启用;AUTO_DETECT 则META-INF/apo.xml的存在性决定是否启用。前者最终也依赖该配置文件;
接下来,进入真正的weave逻辑。
2. Weave过程
-
初始化Load Time Weaver
Spring中的Load Time Weaver默认是DefaultContextLoadTimeWeaver。该类根据实际的运行环境初始化具体的LTW。Spring中提供了3种LTW,ServerSpecificLoadTimeWeaver、InstrumentationLoadTimeWeaver和ReflectiveLoadTimeWeaver。其中InstrumentationLoadTimeWeaver依赖spring-instrument.jar。 -
在Weaver上添加Transformer
这里的Transformer则最终依赖到了AspectJ的ClassPreProcessor。从这里可以看到整个Transformer过程就是AspectJ的处理逻辑了。
public class ClassPreProcessorAgentAdapter implements ClassFileTransformer {
private static ClassPreProcessor classPreProcessor;
static {
try {
classPreProcessor = new Aj();
classPreProcessor.initialize();
} catch (Exception e) {
throw new ExceptionInInitializerError("could not initialize JSR163 preprocessor due to: " + e.toString());
}
}
/**
* Invokes the weaver to modify some set of input bytes.
*
* @param loader the defining class loader
* @param className the name of class being loaded
* @param classBeingRedefined is set when hotswap is being attempted
* @param protectionDomain the protection domain for the class being loaded
* @param bytes the incoming bytes (before weaving)
* @return the woven bytes
*/
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] bytes) throws IllegalClassFormatException {
if (classBeingRedefined != null) {
System.err.println("INFO: (Enh120375): AspectJ attempting reweave of '" + className + "'");
classPreProcessor.prepareForRedefinition(loader, className);
}
return classPreProcessor.preProcess(className, bytes, loader, protectionDomain);
}
}
总结
以上就是今天要聊的全部内容,对LTW的细节做了进一步的探讨。