跳到主要内容
版本:7.0.3

在Spring应用程序中使用AspectJ

Hunyuan 7b 中英对照 Using AspectJ with Spring Applications

到目前为止,我们在本章中所学的内容全部都是关于Spring AOP的。在本节中,我们将探讨当你的需求超出了Spring AOP本身所提供的功能时,如何使用AspectJ编译器或编织器来替代或补充Spring AOP。

Spring自带了一个小的AspectJ方面库,该库在您的发行版中以spring-aspects.jar的格式单独提供。为了使用其中的方面,您需要将其添加到类路径中。使用AspectJ通过Spring进行领域对象依赖注入其他用于AspectJ的Spring方面介绍了这个库的内容以及如何使用它。使用Spring IoC配置AspectJ方面讲述了如何通过AspectJ编译器对AspectJ方面进行依赖注入。最后,在Spring框架中使用AspectJ进行运行时编织为使用AspectJ的Spring应用程序提供了运行时编织的简介。

使用AspectJ通过Spring进行领域对象的依赖注入

Spring容器会实例化并配置在应用程序上下文中定义的Bean。也可以要求Bean工厂根据包含要应用的配置的Bean定义名称来配置一个已存在的对象。spring-aspects.jar中包含一个基于注解的切面(aspect),该切面利用这一功能来实现对任何对象的依赖注入。这种支持旨在用于那些不在任何容器控制之下的对象。领域对象(domain objects)通常属于这一类别,因为它们往往是通过new运算符程序性地创建的,或者是由ORM工具根据数据库查询结果创建的。

@Configurable 注解用于标记一个类适合进行 Spring 驱动的配置。在最简单的情况下,你可以仅将其用作一个标记注解,如下例所示:

package com.xyz.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
// ...
}

当以这种方式用作标记接口时,Spring 会使用与完全限定类型名(com.xyz.domain.Account)相同的名称来配置带注释类型的新的实例(本例中为 Account)。由于通过 XML 定义的bean的默认名称是其类型的完全限定名称,因此声明原型定义的一种便捷方法是省略 id 属性,如下例所示:

<bean class="com.xyz.domain.Account" scope="prototype">
<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>

如果你想明确指定要使用原型bean定义的名称,可以直接在注解中指定,如下例所示:

package com.xyz.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable("account")
public class Account {
// ...
}

Spring现在会寻找一个名为“account”的bean定义,并使用该定义来配置新的Account实例。

你也可以使用自动配置来完全避免指定专门的Bean定义。要让Spring应用自动配置,可以使用@Configurable注解的autowire属性。你可以分别指定@Configurable(autowire=Autowire.BY_TYPE)@Configurable(autowire=Autowire.BY_NAME),以实现按类型或按名称的自动配置。作为一种替代方案,更推荐在字段或方法级别通过@Autowired@Inject来为你的@Configurable Bean指定明确的、基于注解的依赖注入(有关更多详细信息,请参见基于注解的容器配置

最后,你可以通过使用dependencyCheck属性(例如@Configurable(autowire=Autowire.BY_NAME, dependencyCheck=true))来为新创建和配置的对象中的对象引用启用Spring依赖检查。如果将此属性设置为true,Spring在配置完成后会验证所有属性(非基本类型或集合类型)是否都已设置。

请注意,单独使用该注解本身是没有任何效果的。真正发挥作用的是 spring-aspects.jar 中的 AnnotationBeanConfigurerAspect。从本质上讲,这个切面(aspect)的意思是:“在从被 @Configurable 注解标记的类型的新对象初始化完成后,根据该注解的属性,使用 Spring 对新创建的对象进行配置”。在此上下文中,“初始化”既指新实例化的对象(例如,通过 new 操作符创建的对象),也指正在经历反序列化的 Serializable 对象(例如,通过 readResolve() 进行的反序列化)。

备注

上述段落中的一个关键短语是“in essence”(本质上)。在大多数情况下,“after returning from the initialization of a new object”(新对象初始化完成后)这一表述的含义是没问题的。在这里,“after initialization”指的是依赖项在对象构造完成之后才被注入。这意味着这些依赖项在类的构造函数体内是无法使用的。如果你希望依赖项在构造函数体执行之前就被注入,从而可以在构造函数体内使用,那么你需要在@Configurable注解上进行相应设置,如下所示:

@Configurable(preConstruction = true)

关于AspectJ中各种切点(pointcut)类型的语言语义,你可以在AspectJ编程指南此附录中找到更多信息。

要使这生效,这些带注释的类型必须与AspectJ编织器结合使用。你可以使用编译时的Ant或Maven任务来实现这一点(例如,参见AspectJ开发环境指南),或者使用加载时编织(参见在Spring框架中使用AspectJ进行加载时编织)。AnnotationBeanConfigurerAspect本身需要由Spring进行配置(以便获得用于配置新对象的bean工厂的引用)。你可以按照以下方式定义相关配置:

@Configuration
@EnableSpringConfigured
public class ApplicationConfiguration {
}

在配置切面之前创建的@Configurable对象实例会导致一条消息被记录到调试日志中,且该对象不会被进行任何配置。例如,在Spring配置中,某个bean在由Spring初始化时创建了领域对象(domain objects)。在这种情况下,你可以使用depends-on bean属性来手动指定该bean依赖于配置切面。以下示例展示了如何使用depends-on属性:

<bean id="myService"
class="com.xyz.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

<!-- ... -->

</bean>
备注

除非你确实需要在运行时依赖@Configurable的语义,否则不要通过bean配置器方面(aspect)来激活该注解的处理。特别是,请确保不要在那些作为普通Spring bean注册到容器中的bean类上使用@Configurable。这样做会导致双重初始化:一次是通过容器进行初始化,另一次则是通过方面进行初始化。

单元测试 @Configurable 对象

@Configurable 支持的目标之一是实现领域对象的独立单元测试,避免与硬编码查找相关的困难。如果 @Configurable 类型没有被 AspectJ 编织(即没有通过 AspectJ 的机制进行动态处理),那么在单元测试期间这个注解将不起任何作用。你可以为被测试对象设置模拟(mock)或存根(stub)的属性引用,然后正常进行测试。如果 @Configurable 类型已经通过 AspectJ 进行了编织,你仍然可以像往常一样在容器外部进行单元测试,但每次构建 @Configurable 对象时都会看到一条警告信息,提示该对象尚未被 Spring 配置。

处理多个应用程序上下文

用于实现@Configurable支持的AnnotationBeanConfigurerAspect是一个AspectJ单例方面(singleton aspect)。单例方面的作用域与static成员的作用域相同:每个定义该类型的ClassLoader中只有一个方面实例。这意味着,如果你在相同的ClassLoader层次结构中定义了多个应用上下文(application contexts),那么你就需要考虑在哪里定义@EnableSpringConfigured bean,以及将spring-aspects.jar放在类路径(classpath)的什么位置。

考虑一个典型的Spring Web应用程序配置,其中有一个共享的父应用程序上下文(shared parent application context),该上下文定义了常见的业务服务以及支持这些服务所需的一切资源;每个Servlet还有一个子应用程序上下文(child application context),其中包含了该Servlet特有的配置信息。所有这些上下文都存在于同一个ClassLoader层次结构中,因此AnnotationBeanConfigurerAspect只能引用它们中的一个。在这种情况下,我们建议在共享的(父)应用程序上下文中定义@EnableSpringConfigured bean。这个bean定义了您可能想要注入到领域对象(domain objects)中的服务。由此产生的一个后果是,您无法使用@Configurable机制来配置那些包含子(Servlet特定)上下文中定义的bean引用的领域对象(不过这可能本来也不是您想要做的事情)。

当在同一个容器中部署多个Web应用程序时,要确保每个Web应用程序都能通过使用自己的ClassLoader来加载spring-aspects.jar中的类(例如,将spring-aspects.jar放在WEB-INF/lib目录中)。如果spring-aspects.jar仅被添加到容器范围内的类路径中(因此由共享的父ClassLoader加载),那么所有Web应用程序就会共享同一个切面实例(这可能不是你想要的结果)。

其他用于AspectJ的Spring特性

除了@Configurable注解外,spring-aspects.jar还包含一个AspectJ方面(Aspect),您可以使用它来驱动带有@Transactional注解的类型和方法的Spring事务管理。这主要是为那些希望在Spring容器之外使用Spring框架的事务支持的用户准备的。

解释@Transactional注解的方面(aspect)是AnnotationTransactionAspect。当你使用这个方面时,你必须对实现类(或该类中的方法)进行标注,而不是对该类实现的接口(如果有的话)进行标注。AspectJ遵循Java的规则,即接口上的注解不会被继承。

类上的@Transactional注解指定了该类中任何公共操作执行的默认事务语义。

类内部的方法上添加@Transactional注解会覆盖该类注解所规定的默认事务语义(如果存在的话)。任何可见性的方法都可以添加此注解,包括私有方法。直接对非公共方法进行注解是实现这些方法执行时的事务划分的唯一方式。

提示

从Spring Framework 4.2开始,spring-aspects提供了一个类似的切面(aspect),它为标准的jakarta.transactionTransactional注解提供了完全相同的功能。详情请参阅JtaAnnotationTransactionAspect

对于那些想要使用Spring的配置和事务管理支持,但不想(或不能)使用注解的AspectJ程序员来说,spring-aspects.jar还包含了abstract方面的代码,你可以扩展这些方面来提供自己定义的切点(pointcut)。有关更多信息,请查看AbstractBeanConfigurerAspectAbstractTransactionAspect方面的源代码。例如,以下代码片段展示了如何编写一个方面,以便通过使用与完全限定类名匹配的原型bean定义来配置领域模型中定义的所有对象实例:

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

public DomainObjectConfiguration() {
setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
}

// the creation of a new bean (any object in the domain model)
protected pointcut beanCreation(Object beanInstance) :
initialization(new(..)) &&
CommonPointcuts.inDomainModel() &&
this(beanInstance);
}

使用 Spring IoC 配置 AspectJ 切面

当你在Spring应用程序中使用AspectJ方面(aspects)时,很自然地会希望并且期望能够通过Spring来配置这些方面。AspectJ运行时本身负责方面的创建,而通过Spring来配置由AspectJ创建的方面的方式,则取决于该方面所使用的AspectJ实例化模型(per-xxx子句)。

大多数AspectJ方面(aspects)都是单例方面(singleton aspects)。这些方面的配置非常简单。你可以像平常一样创建一个引用方面类型的bean定义,并添加factory-method="aspectOf"这个bean属性。这样就可以确保Spring通过向AspectJ请求来获取方面实例,而不是尝试自己创建实例。以下示例展示了如何使用factory-method="aspectOf"属性:

<bean id="profiler" class="com.xyz.profiler.Profiler"
factory-method="aspectOf"> // <1>

<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
  • 注意 factory-method="aspectOf" 属性

非单例的切面更难以配置。然而,可以通过创建原型bean定义,并利用spring-aspects.jar提供的@Configurable支持,在AspectJ运行时创建bean之后再对这些切面实例进行配置来实现这一目标。

如果你有一些@AspectJ切面想要与AspectJ结合使用(例如,对领域模型类型进行加载时织入),同时还有其他一些@AspectJ切面想要与Spring AOP一起使用,而这些切面都是在Spring中配置的,那么你需要告诉Spring AOP的自动代理支持机制,在配置中定义的@AspectJ切面中应该使用哪一部分来进行自动代理。你可以通过在声明中使用一个或多个元素来实现这一点。每个元素指定一个名称模式,只有那些名称与至少一个模式匹配的bean才会被用于Spring AOP的自动代理配置。以下示例展示了如何使用元素:

<aop:aspectj-autoproxy>
<aop:include name="thisBean"/>
<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
备注

不要被 <aop:aspectj-autoproxy/> 元素的名称所迷惑。使用它会导致创建 Spring AOP 代理。这里使用了 @AspectJ 风格的切面声明,但并没有涉及到 AspectJ 运行时。

在Spring框架中使用AspectJ进行运行时编织

加载时编织(Load-time weaving, LTW)是指在应用程序的类文件被加载到Java虚拟机(JVM)过程中,将AspectJ方面(AspectJ aspects)编织到这些类文件中的过程。本节的重点是在Spring框架的特定环境下配置和使用LTW。本节并非对LTW的全面介绍。如需了解LTW的详细信息,以及如何仅使用AspectJ进行LTW配置(完全不涉及Spring),请参阅AspectJ开发环境指南中的LTW部分

Spring框架为AspectJ LTW带来的价值在于它能够实现对织入(weaving)过程更细致的控制。传统的AspectJ LTW是通过使用Java(5+)代理来实现的,该代理在启动JVM时通过指定一个VM参数来启用。因此,这是一种适用于整个JVM的设置,在某些情况下可能没问题,但往往过于粗糙。而借助Spring支持的LTW,你可以根据每个ClassLoader来单独启用或禁用LTW,这种控制更加细致,也更适用于“单个JVM多个应用程序”的环境(比如在典型的应用服务器环境中)。

此外,在某些环境中,这种支持可以实现在不修改应用程序服务器启动脚本的情况下进行运行时织入(load-time weaving)。通常情况下,修改启动脚本需要添加 -javaagent:path/to/aspectjweaver.jar 或者(如本节后面所描述的)-javaagent:path/to/spring-instrument.jar。开发者负责配置应用程序上下文以实现运行时织入,而无需依赖管理员,因为管理员通常负责部署配置工作,比如启动脚本的设置。

现在销售说辞已经结束,让我们先快速看一下一个使用Spring的AspectJ LTW示例,然后再详细讲解示例中引入的各种元素。要查看完整的示例,请参阅基于Spring Framework的Petclinic样例应用程序

第一个例子

假设你是一名应用程序开发人员,任务是诊断系统中的一些性能问题。我们不会使用复杂的性能分析工具,而是先启用一个简单的性能分析功能,以便快速获取一些性能指标。之后我们可以立即对那个特定区域使用更精细的性能分析工具进行进一步的分析。

备注

此处提供的示例使用了XML配置。您也可以使用Java配置来配置和使用@AspectJ。具体来说,您可以使用@EnableLoadTimeWeaving注解来替代<context:load-time-weaver/>(详情请参见下方)。

以下示例展示了性能分析(profiling)的方面,其实并没有什么特别复杂的地方。这是一个基于时间的性能分析器,它使用了@AspectJ风格的切面(aspect)声明:

package com.xyz;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;

@Aspect
public class ProfilingAspect {

@Around("methodsToBeProfiled()")
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();
System.out.println(sw.prettyPrint());
}
}

@Pointcut("execution(public * com.xyz..*.*(..))")
public void methodsToBeProfiled(){}
}

我们还需要创建一个META-INF/aop.xml文件,以告知AspectJ织造器我们希望将ProfilingAspect织入到我们的类中。这种文件规范,即Java类路径上存在名为META-INF/aop.xml的文件(或多个文件),是AspectJ的标准做法。以下示例展示了aop.xml文件的内容:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

<weaver>
<!-- only weave classes in our application-specific packages and sub-packages -->
<include within="com.xyz..*"/>
</weaver>

<aspects>
<!-- weave in just this aspect -->
<aspect name="com.xyz.ProfilingAspect"/>
</aspects>

</aspectj>
备注

建议仅编织特定的类(通常是应用程序包中的类,如上面aop.xml示例中所示),以避免诸如AspectJ转储文件和警告之类的副作用。从效率的角度来看,这也是一个最佳实践。

现在我们可以继续讨论与Spring相关的配置部分了。我们需要配置一个LoadTimeWeaver(后面会有详细解释)。这个加载时织入器(load-time weaver)是负责将一个或多个META-INF/aop.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">

<!-- a service object; we will be profiling its methods -->
<bean id="entitlementCalculationService"
class="com.xyz.StubEntitlementCalculationService"/>

<!-- this switches on the load-time weaving -->
<context:load-time-weaver/>
</beans>

现在所有所需的文件(aspect、META-INF/aop.xml 文件以及 Spring 配置)都已准备就绪,我们可以创建以下带有 main(..) 方法的驱动类来演示 LTW 的实际应用:

package com.xyz;

// imports

public class Main {

public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

EntitlementCalculationService service =
ctx.getBean(EntitlementCalculationService.class);

// the profiling aspect is 'woven' around this method execution
service.calculateEntitlement();
}
}

我们还有最后一件事要做。本节的介绍中确实提到,可以使用Spring在每个ClassLoader的基础上选择性地启用LTW,这是正确的。然而,在这个例子中,我们使用了一个Java代理(随Spring一起提供的)来启用LTW。我们使用以下命令来运行前面展示的Main类:

java -javaagent:C:/projects/xyz/lib/spring-instrument.jar com.xyz.Main

-javaagent 是一个用于指定和启用 在 JVM 上运行的程序的监控代理 的标志。Spring Framework 自带了这样一个代理,即 InstrumentationSavingAgent,它被打包在 spring-instrument.jar 文件中,在前面的示例中,该文件就是作为 -javaagent 参数的值提供的。

Main 程序执行后的输出类似于下面的示例。(我在 calculateEntitlement() 的实现中加入了一条 Thread.sleep(..) 语句,以便分析工具能够捕获到非 0 毫秒的时间值(“01234” 毫秒并非由 AOP 引入的开销)。以下列表显示了我们运行分析工具时获得的输出结果:

Calculating entitlement

StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms % Task name
------ ----- ----------------------------
01234 100% calculateEntitlement

由于这种LTW(静态代理)是通过使用完整的AspectJ实现的,我们不仅限于对Spring bean进行操作。下面是对Main程序所做的轻微修改,但仍然能够得到相同的结果:

package com.xyz;

// imports

public class Main {

public static void main(String[] args) {
new ClassPathXmlApplicationContext("beans.xml");

EntitlementCalculationService service =
new StubEntitlementCalculationService();

// the profiling aspect will be 'woven' around this method execution
service.calculateEntitlement();
}
}

请注意,在前面的程序中,我们首先启动了Spring容器,然后完全在Spring的上下文之外创建了一个新的StubEntitlementCalculationService实例。不过,性能分析的代码仍然被嵌入其中。

诚然,这个例子过于简化了。然而,Spring中LTW(Low-Level Transaction)支持的基本原理在之前的例子中已经全部介绍过了,本节的其余部分将详细解释每项配置和用法背后的“原因”。

备注

本示例中使用的ProfilingAspect可能比较基础,但它非常实用。这是一个很好的示例,展示了开发人员在开发过程中可以使用的方面(aspect),之后可以很容易地将其从部署到UAT或生产环境的应用程序构建中排除掉。

方面

在LTW中使用的方面(aspects)必须是AspectJ方面的。你可以直接使用AspectJ语言来编写这些方面,也可以采用@AspectJ风格的写法。这样编写出的方面既符合AspectJ的规范,也是Spring AOP的方面。此外,编译后的方面类需要被包含在类路径(classpath)中才能被使用。

META-INF/aop.xml

AspectJ LTW(LinkTime Weaving)的基础设施是通过使用一个或多个位于Java类路径上的META-INF/aop.xml文件来配置的(这些文件可以直接放在类路径上,或者更常见的情况是,放在jar文件中)。例如:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

<weaver>
<!-- only weave classes in our application-specific packages and sub-packages -->
<include within="com.xyz..*"/>
</weaver>

</aspectj>
备注

建议仅编织特定的类(通常是应用程序包中的那些类,如上面的 aop.xml 示例所示),以避免诸如 AspectJ 堆栈跟踪文件和警告之类的副作用。从效率的角度来看,这也是一个最佳实践。

该文件的结构和内容在AspectJ参考文档的LTW部分中有详细说明。由于aop.xml文件完全是由AspectJ编写的,因此我们在这里不再对其进行进一步描述。

所需库(JARS)

至少,你需要以下库来使用Spring框架对AspectJ LTW的支持:

  • spring-aop.jar

  • aspectjweaver.jar

如果你使用Spring提供的代理来启用仪表化,你还需要:

  • spring-instrument.jar

Spring 配置

在Spring的LTW(动态代理)支持中,关键组件是LoadTimeWeaver接口(位于org.springframework.instrument.classloading包中),以及随Spring发行版一起提供的众多该接口的实现。LoadTimeWeaver负责在运行时向ClassLoader中添加一个或多个java.lang.instrument.ClassFileTransformers,这为各种有趣的应用打开了大门,其中之一就是方面的动态代理(Aspectual LTW)。

提示

如果您不熟悉运行时类文件转换的概念,在继续之前,请查看java.lang.instrument包的Javadoc API文档。虽然该文档并不全面,但至少您可以从中看到关键的接口和类(在阅读本节内容时可以作为参考)。

为特定的ApplicationContext配置LoadTimeWeaver只需添加一行代码即可。(请注意,你几乎肯定需要使用ApplicationContext作为Spring容器——通常,仅使用BeanFactory是不够的,因为LoadTimeWeaver的机制依赖于BeanFactoryPostProcessors。)

要启用Spring框架的LTW(Load-Time Weaving)支持,你需要按照以下方式配置一个LoadTimeWeaver

@Configuration
@EnableLoadTimeWeaving
public class ApplicationConfiguration {
}

上述配置会自动为您定义并注册一些特定于LTW的基础设施Bean,例如LoadTimeWeaverAspectJWeavingEnabler。默认的LoadTimeWeaverDefaultContextLoadTimeWeaver类,它会尝试装饰自动检测到的LoadTimeWeaver。被“自动检测”到的LoadTimeWeaver的具体类型取决于您的运行时环境。下表总结了各种LoadTimeWeaver的实现:

表1. DefaultContextLoadTimeWeaver LoadTimeWeavers

运行时环境LoadTimeWeaver 实现方式
Apache Tomcat 中运行TomcatLoadTimeWeaver
GlassFish 中运行(仅限 EAR 部署)GlassFishLoadTimeWeaver
在 Red Hat 的 JBoss ASWildFly 中运行JBossLoadTimeWeaver
使用 Spring 的 InstrumentationSavingAgent 启动 JVM (java -javaagent:path/to/spring-instrument.jar)InstrumentationLoadTimeWeaver
作为备选方案,期望底层的 ClassLoader 遵循常见约定(即具有 addTransformer 方法,可选地还具备 getThrowawayClassLoader 方法)ReflectiveLoadTimeWeaver

请注意,该表格仅列出了在使用DefaultContextLoadTimeWeaver时自动检测到的LoadTimeWeavers。您可以指定要使用的具体LoadTimeWeaver实现。

要配置特定的LoadTimeWeaver,需要实现LoadTimeWeavingConfigurer接口并重写getLoadTimeWeaver()方法(或使用相应的XML配置方式)。以下示例指定了一个ReflectiveLoadTimeWeaver

@Configuration
@EnableLoadTimeWeaving
public class CustomWeaverConfiguration implements LoadTimeWeavingConfigurer {

@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
}

通过配置定义和注册的LoadTimeWeaver,之后可以通过其众所周知的名称loadTimeWeaver从Spring容器中获取。请记住,LoadTimeWeaver仅作为Spring的LTW(动态代理)基础设施的一种机制存在,用于添加一个或多个ClassFileTransformer。实际执行LTW操作的ClassFileTransformerClassPreProcessorAgentAdapter类(来自org.aspectj.weaver.loadtime包)。有关更多详细信息,请参阅ClassPreProcessorAgentAdapter类的类级Javadoc,因为动态代理的具体实现细节超出了本文档的讨论范围。

还有一点关于配置的属性需要讨论:aspectjWeaving属性(如果你使用XML格式,则为aspectj-weaving)。这个属性控制是否启用LTW(Low-Level Weaving)。它接受三个可能的值之一,如果该属性不存在,默认值为autodetect。下表总结了这三个可能的值:

表2. AspectJ编织属性值

注释值XML值说明
ENABLEDonAspectJ编织处于启用状态,方面(aspects)会在加载时根据需要进行编织。
DISABLEDoffLTW(Load-Time Weaving)处于禁用状态。加载时不会进行任何方面编织。
AUTODETECTautodetect如果Spring的LTW基础设施能够找到至少一个META-INF/aop.xml文件,则AspectJ编织处于启用状态;否则,会处于禁用状态。这是默认值。

环境特定配置

本节最后部分包含了在应用程序服务器和Web容器等环境中使用Spring的LTW支持时所需的任何其他设置和配置。

Tomcat, JBoss, WildFly

Tomcat和JBoss/WildFly提供了一个通用的应用程序ClassLoader,该ClassLoader支持本地插装(local instrumentation)。Spring的原生LTW(Link-Time Weaving)可以利用这些ClassLoader实现来进行AspectJ织造(AspectJ weaving)。你可以像前面描述的那样简单地启用运行时织造功能。具体来说,你不需要修改JVM启动脚本来添加-javaagent:path/to/spring-instrument.jar参数。

请注意,在JBoss上,您可能需要禁用应用服务器的扫描功能,以防止它在应用程序实际启动之前就加载这些类。一个快速的解决方法是在您的artifacts中添加一个名为WEB-INF/jboss-scanning.xml的文件,并在其中写入以下内容:

<scanning xmlns="urn:jboss:scanning:1.0"/>

通用Java应用程序

当在特定 LoadTimeWeaver 实现不支持的环境中需要类内联(class instrumentation)时,JVM 代理(JVM agent)通常是一个解决方案。对于这类情况,Spring 提供了 InstrumentationLoadTimeWeaver,它需要一个特定于 Spring 的(但非常通用的)JVM 代理文件 spring-instrument.jar,该代理文件会通过常见的 @EnableLoadTimeWeaving<context:load-time-weaver/> 配置自动被检测到。

要使用它,你必须通过提供以下JVM选项来启动带有Spring代理的虚拟机:

-javaagent:/path/to/spring-instrument.jar

请注意,这需要修改JVM启动脚本,因此在应用服务器环境中可能无法使用这种方法(具体取决于你的服务器和操作政策)。不过,对于每个JVM运行一个应用的部署场景(例如独立的Spring Boot应用程序),通常情况下你还是可以完全控制整个JVM的配置的。