代码织入实现方式:
静态代理
AspectJ 织入器 weaver)
compile-time weaving 使用 aspectj 编译器进行编译源码
post-compile weaving 对 class 文件进行织入
load-time weaving(LTW) 当 class loader 加载类的时候,进行织入
动态代理
JDK 动态代理 (接口)
CGlib(类)
这里使用 AspectJ LTW 实现, 这种方式在类加载器织入代码.
编译器织入 会造成编译速度变慢, 而且必须使用 ajc 编译器
动态代理 会生成大量代理类, 加速内存消耗
因此使用 类加载期织入 相对于其他两种方式, 更加轻便.
LTW(Load Time Weaver),即加载期切面织入,是 ApsectJ 切面织入的一种方式,它通过 JVM 代理在类加载期替换字节码到达织入切面的目的。
具体实现 首先定义个切面类,该切面功能非常简单,就是在 com.xxx.server.rest.resource.impl
及其所有子包下所有的类的 public 方法调用后打印执行时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Slf4j @Aspect public class ProfilingAspect { @Pointcut("execution(* com.xxx.server.rest.resource.impl..*.*(..))") public void api () { } @Around("api()") public Object profile (ProceedingJoinPoint pjp) throws Throwable { StopWatch sw = new StopWatch (getClass().getSimpleName()); try { sw.start(pjp.getSignature().getName()); return pjp.proceed(); } finally { sw.stop(); log.debug("AOP --> {} ms" , sw.getTotalTimeMillis()); } } }
使用了 AspectJ 注解,想要了解更多的 Aspect 注解使用可以查看 AspectJ 相关的文档,在 https://github.com/eclipse/org.aspectj/tree/master/docs
下面有个 quick5.doc 文档列出了所有的注解,也可以查看源代码或者反编译 aspectweaver.jar,查看里面有哪些注解,在 org.aspectj.lang.annotation 这个包下面 编写目标测试 bean:
1 2 3 4 5 public class LTWBean { public void run () { System.out.println("LTWBean run..." ); } }
编写一个 XML 文件定义切面,该文件名及其所在路径只能固定的几种:META-INF/aop.xml、META-INF/aop-ajc.xml、org/aspectj/aop.xml,文件内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="UTF-8" ?> <aspectj > <weaver > <include within ="com.xxx.server.rest.resource.impl.*" /> <include within ="com.xxx.trace.client.aspect.*" /> </weaver > <aspects > <aspect name ="com.xxx.trace.client.aspect.ProfilingAspect" /> </aspects > </aspectj > >
存在的坑
按照官方文档配置 aop.xml, 一直不生效, 是因为没有将切面类 include
文件定义了切面以及切面编织器,这里的切面会被织入 com.xxx.server.rest.resource.impl
及其子包下的所有类。 编织器注入 Spring 容器中,并且定义目标测试 bean:
1 2 3 <bean id ="loadTimeWeaver" class ="org.springframework.context.weaving.DefaultContextLoadTimeWeaver" /> <bean class ="org.springframework.context.weaving.AspectJWeavingEnabler" /> <bean id ="ltwBean" class ="spring.beans.ltw.LTWBean" lazy-init ="true" />
上面的编织器 bean 的名称必须是 loadTimeWeaver,此外还有一种更简单的配置方式,使用 context 名称空间下的标签:
1 2 <context:load-time-weaver aspectj-weaving ="autodetect" /> <bean id ="ltwBean" class ="spring.beans.ltw.LTWBean" > </bean >
上面两段配置起到的效果完全是一样的,bean 解析器在解析到 context:load-time-weaver
标签时, 会自动生成一个名称为 loadTimeWeaver
类型 org.springframework.context.weaving.DefaultContextLoadTimeWeaver
的 bean 以及一个类型 org.springframework.context.weaving.AspectJWeavingEnabler
的匿名 bean,这段代码在 LoadTimeWeaverBeanDefinitionParser
类中.
aspectj-weaving 属性有三个枚举值:
分别是打开、关闭、自动检测
这里设置成 autodetect,容器会检查程序是否定义了切面定义文件(即上面提到的 aop.xml 文件) 代码在 LoadTimeWeaverBeanDefinitionParser
的 isAspectJWeavingEnabled
方法。
编写测试代码:
1 2 3 BeanFactory context = new ClassPathXmlApplicationContext ("spring/beans/ltw/ltw.xml" );LTWBean ltwBean = (LTWBean) context.getBean("ltwBean" );ltwBean.run();
运行测试代码,在运行时需要启动一个 JVM 代理,首先需要下载 spring-instrument-xxx.jar 包,在虚拟机参数中添加
1 2 -javaagent:/path/to/spring-instrument-4.3.10.RELEASE.jar -javaagent:/path/to/aspectjweaver-1.8.10.jar
执行测试代码,打印下面日志,说明切面织入成功:
1 2 LTWBean run... AOP --> xx ms
字节码转换和虚拟机代理 要了解 LTW 的原理,首先要对 JDK 的字节码转换框架和 JVM 代理有一定的了解:
从 JAVA5 开始,在 JDK 中添加了一个新包 java.lang.instrument,这个包就是字节码转换框架的基础。字节码转换框架是一项非常重要的技术,许多程序监控和调试工具比如 BTrace 都是在这个框架的基础上实现的。这个包下面有两个关键接口:
ClassFileTransformer:类字节码转换器,提供了一个 transform 方法,这个方法的用来转换提供的类字节码,并返回一个新的替换字节码。不同于 CGLIB、JDK 动态代理等字节码操作技术,ClassFileTransformer 是彻底的替换掉原类,而 CGLIB 和 JDK 动态代理是生成一个新子类或接口实现。
Instrumentation:这个类提供检测 Java 编程语言代码所需的服务。检测是向方法中添加字节码,以搜集各种工具所使用的数据。由于更改完全是进行添加,所以这些工具不修改应用程序的状态或行为。这种无害工具的例子包括镜像代理、分析器、覆盖分析器和事件记录器。可以通过它的 addTransformer 方法把实现好的字节码转换器(ClassFileTransformer)注册到 JVM 中。
在普通代码中无需实现 Instrumentation 并且创建 Instrumentation 实例,要获取 Instrumentation 实例,可以通过 JVM 代理,JVM 代理可以通过两种方式启动:
程序启动时启动代理 在程序启动时指定代理,这时候虚拟机会创建一个 Instrumentation 实例实例传递给代理类的 premain 方法。需要在 META-INF/MANIFEST.MF 中通过 Premain-Class 指定代理入口类,并且代理入口类中必须定义 premain 方法,像上面提到的在运行程序时在虚拟机参数添加 -javaagent 就是在程序启动时指定了代理,我们可以看看 spring-instrument-3.2.9.RELEASE.jar 包中 MANIFEST.MF 文件的内容:
1 2 3 4 5 6 7 8 Manifest-Version: 1.0 Created-By: 1.7.0_55 (Oracle Corporation) Implementation-Title: spring-instrument Implementation-Version: 3.2.9.RELEASE Premain-Class: org.springframework.instrument.InstrumentationSavingAgent Can-Redefine-Classes: true Can-Retransform-Classes: true Can-Set-Native-Method-Prefix: false
看以看到通过 Premain-Class 指定了 org.springframework.instrument.InstrumentationSavingAge 作为代理入口类,看看 InstrumentationSavingAge 这个类的代码,有一个 premain 方法并且有一个 Instrumentation 参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class InstrumentationSavingAgent { private static volatile Instrumentation instrumentation; public static void premain (String agentArgs, Instrumentation inst) { instrumentation = inst; } public static Instrumentation getInstrumentation () { return instrumentation; } }
程序运行时启动代理 还有一种方式是在程序启动之后启动代理,这种情况下可以在不暂停应用程序的情况下动态向注册类转换器对已加载的类进行重定义。这种方式需要在 META-INF/MANIFEST.MF 文件中通过 Agent-Class 指定代理入口类,并且入口代理类中定义 agentmain 方法,虚拟机把 Instrumentation 实例传递给这个 agentmain 方法,下面代码是 Druid 框架(Druid 是阿里巴巴一个开源的连接池框架)中拷贝过来的一段代码,演示了如何在应用运行时启动一个 JMX 代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 private static String loadManagementAgentAndGetAddress (int vmid) throws IOException { VirtualMachine vm = null ; String name = String.valueOf(vmid); try { vm = VirtualMachine.attach(name); } catch (AttachNotSupportedException x) { throw new IOException (x.getMessage(), x); } String home = vm.getSystemProperties().getProperty("java.home" ); String agent = home + File.separator + "jre" + File.separator + "lib" + File.separator + "management-agent.jar" ; File f = new File (agent); if (!f.exists()) { agent = home + File.separator + "lib" + File.separator + "management-agent.jar" ; f = new File (agent); if (!f.exists()) { throw new IOException ("Management agent not found" ); } } agent = f.getCanonicalPath(); try { vm.loadAgent(agent, "com.sun.management.jmxremote" ); } catch (AgentLoadException x) { throw new IOException (x.getMessage(), x); } catch (AgentInitializationException x) { throw new IOException (x.getMessage(), x); } Properties agentProps = vm.getAgentProperties(); String address = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); vm.detach(); return address; }
运行 management-agent.jar 作为代理,来看看 management-agent.jar 中的 META-INF/MANIFEST.MF 文件,同时指定了 Premain-Class 和 Agent-Class,代理启动之后虚拟机会调用代理入口类的 agentmain 方法,需要注意的是 Agent 类只有一个 String 参数的 agentmain 并没有定义带 Instrumentation 参数的 agentmain,因为 JMX 代理并不需要 Instrumentation 实例:
1 2 3 4 Manifest-Version: 1.0 Premain-Class: sun.management.Agent Created-By: 1.5.0 (Sun Microsystems Inc.) Agent-Class: sun.management.Agent
实现原理 了解了 JDK 字节码框架和虚拟机代理之后,分析 LTW 的实现原理就简单得多了。 当容器检查到定义了名称为 loadTimeWeaver 的 bean 时,会注册一个 LoadTimeWeaverAwareProcessor 到容器中,代码在 AbstractApplicationContext 的 prepareBeanFactory 方法中:
1 2 3 4 5 6 7 8 9 10 11 12 protected void prepareBeanFactory (ConfigurableListableBeanFactory beanFactory) { ... if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor (beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader (beanFactory.getBeanClassLoader())); } ... }
LoadTimeWeaverAwareProcessor 是一个 BPP(BeanPostProcessor),这个 BPP 用来处理 LoadTimeWeaverAware 接口的,把 LTW 实例设置到实现了 LoadTimeWeaverAware 接口的 bean 中,从 LoadTimeWeaverAwareProcessor 的 postProcessBeforeInitialization 方法可以看出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 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; }
再来看下 AspectJWeavingEnabler 这个类的代码,它是一个 BFPP,同时也实现了 LoadTimeWeaverAware 接口,通过上面的分析,loadTimeWeaver 这个 bean 会自动注入到 AspectJWeavingEnabler 类型 bean 中。AspectJWeavingEnabler 的 postProcessBeanFactory 方法直接调用 enableAspectJWeaving 方法,来看看这个方法的代码:
1 2 3 4 5 6 7 8 9 10 11 12 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" ); } } weaverToUse.addTransformer(new AspectJClassBypassingClassFileTransformer ( new ClassPreProcessorAgentAdapter ())); }
weaverToUse 这个参数就是被容器自动注入的 loadTimeWeaver bean,从 bean 定义 XML 中可以知道这个 bean 是 DefaultContextLoadTimeWeaver 类型的,它的 addTransformer 方法代码如下:
1 2 3 public void addTransformer (ClassFileTransformer transformer) { this .loadTimeWeaver.addTransformer(transformer); }
DefaultContextLoadTimeWeaver 类也有个 loadTimeWeaver 属性,这个属性是在 setBeanClassLoader 方法中设置进去的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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()) { 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" ); } } }
在方法里面判断了当前是否存在 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 对象。 接下来就看看 InstrumentationLoadTimeWeaver 类的 addTransformer 方法代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 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." ); } this .instrumentation.addTransformer(actualTransformer); this .transformers.add(actualTransformer); } }
从代码中可以看到,这个方法中,把类转换器 actualTransformer 通过 instrumentation 实例注册给了虚拟机。这里采用了修饰器模式,actualTransformer 对 transformer 进行修改封装,下面是 FilteringClassFileTransformer 这个内部类的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 方法中有一行代码:
1 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 方法:
1 2 3 4 5 6 7 8 9 10 11 12 protected void finishBeanFactoryInitialization (ConfigurableListableBeanFactory beanFactory) { ... beanFactory.setTempClassLoader(null ); beanFactory.freezeConfiguration(); beanFactory.preInstantiateSingletons(); }
再回头来看 FilteringClassFileTransformer 类的 transform 方法,调用 targetTransformer 执行字节码转换。来看看 targetTransformer 这个类转换器是在哪创建的,回头再看下 AspectJWeavingEnabler 类的 enableAspectJWeaving 方法,有下面这行代码:
1 2 weaverToUse.addTransformer(new AspectJClassBypassingClassFileTransformer ( new ClassPreProcessorAgentAdapter ()));
AspectJClassBypassingClassFileTransformer 类和 ClassPreProcessorAgentAdapter 类都实现了字节码转换接口 ClassFileTransformer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static class AspectJClassBypassingClassFileTransformer implements ClassFileTransformer { private final ClassFileTransformer delegate; public AspectJClassBypassingClassFileTransformer (ClassFileTransformer delegate) { this .delegate = delegate; } 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); } }
这也是一个修饰器模式,最终会调用 ClassPreProcessorAgentAdapter 的 transform 方法执行字节码转换逻辑,在类加载器定义类时(即调用 defineClass 方法)会调用此类的 transform 方法来进行字节码转换替换原始类。ClassPreProcessorAgentAdapter 类中的代码比较多,这里就不列出来了,它的主要工作是解析 aop.xml 文件,解析类中的 Aspect 注解,并且根据解析结果来生成转换后的字节码。 在上面例子里面提到的通过 context 名称空间下的 load-time-weaver 标签来配置,其本质原理是一致的。通过在 context 的名称空间处理器 ContextNamespaceHandler 中可以看到 load-time-weaver 标签的解析器是 LoadTimeWeaverBeanDefinitionParser 类,看下这个类的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 class LoadTimeWeaverBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { private static final String WEAVER_CLASS_ATTRIBUTE = "weaver-class" ; private static final String ASPECTJ_WEAVING_ATTRIBUTE = "aspectj-weaving" ; private static final String DEFAULT_LOAD_TIME_WEAVER_CLASS_NAME = "org.springframework.context.weaving.DefaultContextLoadTimeWeaver" ; private static final String ASPECTJ_WEAVING_ENABLER_CLASS_NAME = "org.springframework.context.weaving.AspectJWeavingEnabler" ; @Override protected String getBeanClassName (Element element) { if (element.hasAttribute(WEAVER_CLASS_ATTRIBUTE)) { return element.getAttribute(WEAVER_CLASS_ATTRIBUTE); } return DEFAULT_LOAD_TIME_WEAVER_CLASS_NAME; } @Override protected String resolveId (Element element, AbstractBeanDefinition definition, ParserContext parserContext) { return ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME; } @Override protected void doParse (Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); if (isAspectJWeavingEnabled(element.getAttribute(ASPECTJ_WEAVING_ATTRIBUTE), parserContext)) { RootBeanDefinition weavingEnablerDef = new RootBeanDefinition (); weavingEnablerDef.setBeanClassName(ASPECTJ_WEAVING_ENABLER_CLASS_NAME); parserContext.getReaderContext().registerWithGeneratedName(weavingEnablerDef); if (isBeanConfigurerAspectEnabled(parserContext.getReaderContext().getBeanClassLoader())) { new SpringConfiguredBeanDefinitionParser ().parse(element, parserContext); } } } protected boolean isAspectJWeavingEnabled (String value, ParserContext parserContext) { if ("on" .equals(value)) { return true ; } else if ("off" .equals(value)) { return false ; } else { ClassLoader cl = parserContext.getReaderContext().getResourceLoader().getClassLoader(); return (cl.getResource(AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE) != null ); } } protected boolean isBeanConfigurerAspectEnabled (ClassLoader beanClassLoader) { return ClassUtils.isPresent(SpringConfiguredBeanDefinitionParser.BEAN_CONFIGURER_ASPECT_CLASS_NAME, beanClassLoader); } }
从上面的代码可以看出在解析 load-time-weaver 标签时,从 getBeanClassName 方法中可以看到,如果没有指定 weaver-class 属性,会自动给容器中注入一个 org.springframework.context.weaving.DefaultContextLoadTimeWeaver 类型的 bean,从 resolveId 方法中看到,该 bean 的名称为 loadTimeWeaver。在 doParse 方法中,还会注册一个类型为 org.springframework.context.weaving.AspectJWeavingEnabler 的匿名 bean。从此可以看出下面两段配置完全是等价的:
1 2 3 <bean id ="loadTimeWeaver" class ="org.springframework.context.weaving.DefaultContextLoadTimeWeaver" > </bean > <bean class ="org.springframework.context.weaving.AspectJWeavingEnabler" > </bean >
1 <context:load-time-weaver aspectj-weaving ="autodetect" />