Using AspectJ with Spring Applications

到目前为止,我们在本章中涵盖的所有内容都是纯粹的 Spring AOP。在本部分中,我们将研究如果您需要超出 Spring AOP 单独提供的功能,可以使用 AspectJ 编译器或织入器代替或补充 Spring AOP。 Spring 随一小型的 AspectJ 方面库一起提供,您可在发行版中以独立方式获取该库,作为 spring-aspects.jar。您需要将此库添加到类路径中,以便使用其中的方面。Using AspectJ to Dependency Inject Domain Objects with SpringOther Spring aspects for AspectJ 讨论了此库的内容以及如何使用该库。Configuring AspectJ Aspects by Using Spring IoC 讨论如何使用 AspectJ 编译器编织的依赖注入 AspectJ 方面。最后, Load-time Weaving with AspectJ in the Spring Framework 提供了有关使用 AspectJ 的 Spring 应用程序负载时编织的简介。

Using AspectJ to Dependency Inject Domain Objects with Spring

Spring 容器实例化并配置在您的应用程序上下文中定义的 bean。还可以要求 Bean 工厂配置一个已经存在的对象,给定一个包含要应用的配置的 bean 定义的名称。spring-aspects.jar包含一个注解驱动的方面,它利用此功能允许依赖注入任何对象。该支持旨在用于在任何容器的控制之外创建的对象。域对象通常属于此类别,因为它们通常使用new运算符以编程方式创建,或在数据库查询的结果中使用 ORM 工具创建。

@Configurable注解将一个类标记为符合 Spring 驱动的配置。在最简单的情况下,您可以像以下示例所示那样纯粹将其用作标记注解:

  • Java

  • Kotlin

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

@Configurable
public class Account {
	// ...
}
import org.springframework.beans.factory.annotation.Configurable

@Configurable
class Account {
	// ...
}

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

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

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

  • Java

  • Kotlin

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

@Configurable("account")
public class Account {
	// ...
}
import org.springframework.beans.factory.annotation.Configurable

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

现在 Spring 查找一个名为account的 Bean 定义,并将其用作配置新Account实例的定义。

您还可以使用自动装配来避免必须指定任何专用 Bean 定义。要让 Spring 应用自动装配,请使用 @Configurable 注解的 autowire 属性。您可以指定 @Configurable(autowire=Autowire.BY_TYPE)@Configurable(autowire=Autowire.BY_NAME) 分别通过类型或名称进行自动装配。作为一种选择,最好通过 @Autowired@Inject 在字段或方法级别为 @Configurable Beans 指定显式注解驱动的依赖项注入(有关更多详细信息,请参见 Annotation-based Container Configuration)。

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

请注意,单独使用注解不起作用。spring-aspects.jar 中的 AnnotationBeanConfigurerAspect 是对注解存在形式执行操作的。从本质上讲,此方面表示,“从使用 @Configurable 进行注解的新类型对象的初始化返回后,使用 Spring 根据注解的属性配置新建对象”。在此上下文中,“初始化”是指新实例化的对象(例如,使用 new 运算符实例化的对象),以及正在反序列化的 Serializable 对象(例如,通过 readResolve())。

上面一段中的关键短语之一是“从本质上讲”。对于大多数情况,确切的语义“在对新对象的初始化返回后”都很好。在此上下文中,“初始化后”意味着在对象构建后注入依赖项。这意味着构造函数主体内无法使用依赖项。如果您希望在构造函数主体运行之前注入依赖项,从而可以在构造函数主体中使用它们,则需要在@Configurable声明中定义此内容,如下所示:

Java
@Configurable(preConstruction = true)
Kotlin
@Configurable(preConstruction = true)

您可以在 此附录 中的https://www.eclipse.org/aspectj/doc/released/progguide/index.html[AspectJ 编程指南] 中找到有关各种 AspectJ 切入点类型语言语义的更多信息。

为此,注释类型必须使用 AspectJ weaver 编织。您可以使用构建时 Ant 或 Maven 任务执行此操作(例如,请参阅 AspectJ 开发环境指南),或使用负载时编织(请参阅 Load-time Weaving with AspectJ in the Spring Framework)。AnnotationBeanConfigurerAspect 本身需要由 Spring 配置(为了获得要用来配置新对象的 Bean 工厂的引用)。您可以按如下方式定义相关配置:

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableSpringConfigured
public class ApplicationConfiguration {
}
@Configuration
@EnableSpringConfigured
class ApplicationConfiguration
<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">

	<context:spring-configured />

</beans>

在配置方面之前创建的@Configurable对象实例将导致一条消息输出到调试日志中,并且不会配置对象。一个示例可能是 Spring 配置中的一个 bean,该 bean 在被 Spring 初始化时创建域对象。在这种情况下,您可以使用depends-on bean 属性手动指定 bean 依赖于配置方面。以下示例说明如何使用depends-on属性:

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

	<!-- ... -->

</bean>

除非你真的想在运行时依赖它的语义,否则不要通过 bean 配置器方面激活 @Configurable 处理。特别是,请确保您未在作为常规 Spring bean 使用容器注册的 bean 类上使用 @Configurable。执行此操作会导致双重初始化,一次通过容器,一次通过方面。

Unit Testing @Configurable Objects

@Configurable 中给出的支持的一个目标在于避免进行硬编码的查找,从而支持对领域目标进行独立的单元测试。如果 AspectJ 没有编织 @Configurable 类型,该对象无法在单元测试中产生任何影响。你可以在测试目标中设置模拟或存根的属性引用,并且像往常一样进行操作。如果 @Configurable 类型已经由 AspectJ 编织,你仍然可以在容器之外像往常一样执行单元测试,但每次构建一个 @Configurable 目标时你都将看到一条警告信息,指明该目标尚未由 Spring 配置。

Working with Multiple Application Contexts

用于实现 @Configurable 支持的 AnnotationBeanConfigurerAspect 是一个 AspectJ 单例切面。单例切面的作用域与 static 成员的作用域相同:对于定义类型的每个 ClassLoader,只有一个切面实例。这意味着,如果你在同一个 ClassLoader 层次结构中定义了多个应用程序上下文,你需要考虑在其中定义 @EnableSpringConfigured bean,以及将 spring-aspects.jar 放在类路径的哪里。

考虑一个典型的 Spring Web 应用程序配置,其中有一个共享的父 ApplicationContext,它定义了公共业务服务、支持这些服务所需的一切,以及一个针对每个 Servlet(其中包含对该 servlet 特有的定义)的子 ApplicationContext。所有这些上下文在相同的 ClassLoader 层次结构中并存,因此 AnnotationBeanConfigurerAspect 只能引用其中的一个。在这种情况下,我们建议在共享(父级)应用程序上下文中定义 @EnableSpringConfigured Bean。这定义了你可能想要注入到领域对象中的服务。这样会产生一个后果,即你使用 @Configurable 机制无法通过引用在子(特定 servlet)上下文中定义的 Bean 来配置领域对象(这可能并不是你要做的)。

在同一个容器中部署多个 Web 应用程序时,请确保每个 Web 应用程序使用自己的 ClassLoader 来加载 spring-aspects.jar 中的类型(例如,将 spring-aspects.jar 放置在 WEB-INF/lib 中)。如果仅将 spring-aspects.jar 添加到全容器类路径中(因此由共享的父 ClassLoader 加载),所有 Web 应用程序将共享相同的面向实例(这可能不是您想要的)。

Other Spring aspects for AspectJ

@Configurable 切面外,spring-aspects.jar 还包含一个 AspectJaspect,你可以使用该切面来驱动 Spring 事务管理,类型和方法上带有 @Transactional 注解。这主要适用于希望在 Spring 容器外部使用 Spring Framework 事务支持的用户。

解释 @Transactional 注释的方面是 AnnotationTransactionAspect。使用此方面时,必须对实现类(或该类中的方法或二者)添加注释, وليس 实现了类的接口(如果存在)。AspectJ 遵循 Java 的规则,即接口上的注释不会被继承。

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

类中的某个方法上的 @Transactional 注释会覆盖类注释(如果存在)给出的默认事务语义。任何可见性的方法都可以进行注释,包括私有方法。直接注释非公共方法是唯一的方法来为这些方法的执行获取事务分界。

从 Spring Framework 4.2 开始,spring-aspects 提供了一个类似的方面,它为标准 jakarta.transaction.Transactional 注释提供了完全相同的功能。查看 JtaAnnotationTransactionAspect 了解更多详情。

对于希望使用 Spring 配置和事务管理支持但不想(或不能)使用注释的 AspectJ 程序员来说,“spring-aspects.jar”也包含你可以扩展以提供自己的切点定义的“抽象”方面。有关更多信息,请参阅“AbstractBeanConfigurerAspect”和“AbstractTransactionAspect”方面的来源。例如,以下摘录展示了如何编写一个方面来配置域模型中定义的所有对象实例,方法是使用与完全限定类名称匹配的原型 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);
}

Configuring AspectJ Aspects by Using Spring IoC

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

大多数 AspectJ 方面是单例方面。这些方面的配置很简单。您可以创建一个引用方面类型的 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>
1 Note the factory-method="aspectOf" attribute

非单例方面更难配置。然而,可以通过创建原型 bean 定义并且使用 spring-aspects.jar 中的 @Configurable 支持来配置方面实例,一旦它们被 AspectJ 运行时创建,即可完成。

如果您有一些希望使用 AspectJ 编织的 @AspectJ 方面(例如,为领域模型类型使用加载时间编织),还有一些其他希望与 Spring AOP 一起使用的 @AspectJ 方面,并且这些方面全部都在 Spring 中进行配置,您需要告诉 Spring AOP、@AspectJ 自动代理支持,应为自动代理使用配置中定义的哪一个 @AspectJ 方面的子集。您可以通过在 <aop:aspectj-autoproxy/> 声明中使用一个或多个 <include/> 元素来完成此操作。每个 <include/> 元素指定一个名称模式,并且仅将与至少一个模式匹配的名称 Bean 用于 Spring AOP 自动代理配置。以下示例展示如何使用 <include/> 元素:

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

不要被 <aop:aspectj-autoproxy/> 元素的名称误导。使用它会导致创建 Spring AOP 代理。此处使用了面向 AspectJ 风格的方面声明,但 AspectJ 运行时并未涉及进来。

Load-time Weaving with AspectJ in the Spring Framework

加载时编织 (LTW) 指的是在将 AspectJ 方面编织到应用程序的类文件时,它们正在 Java 虚拟机 (JVM) 中加载的过程。本节的重点是在 Spring 框架的特定上下文中配置和使用 LTW。本节并不是 LTW 的一般性介绍。有关 LTW 的具体细节以及仅使用 AspectJ 配置 LTW 的完整详细信息(不涉及 Spring),请参见 AspectJ 开发环境指南的 LTW 部分

Spring Framework 为 AspectJ LTW 带来的价值在于能够更细粒度地控制 Weaving 过程。“纯”AspectJ LTW 是通过使用 Java(5+)代理来实现的,该代理在启动 JVM 时通过指定 VM 参数来开启。因此,它是 JVM 级别的设置,在某些情况下可能很好,但在很多情况下都有些粗略。Spring 启用的 LTW 让你能够基于按 ClassLoader 级别开启 LTW,这更加细粒度,并且在“单 JVM-多应用程序”环境中(例如典型的应用程序服务器环境中)更有意义。

此外,in certain environments,此支持启用了负载时编织,而无需对应用程序服务器启动脚本进行任何修改,该脚本需要添加 -javaagent:path/to/aspectjweaver.jar 或(如下文所述)-javaagent:path/to/spring-instrument.jar。开发人员配置应用程序上下文以启用负载时编织,而不是依靠通常负责部署配置(例如启动脚本)的管理员。

现在,在产品宣传结束之后,首先让我们快速浏览一个使用 Spring 的 AspectJ LTW 的示例,然后详细了解示例中引入的元素。有关完整示例,请参见 Petclinic 样本应用程序

A First Example

假设您是一位应用程序开发者, tasked 处置诊断系统中某些性能问题的原因。但不是拿出分析工具,我们将开启一个简单的分析环节,让我们快速获取一些性能指标。然后,我们马上就可以对那个特定区域应用一个精细的分析工具。

此处提供的示例使用 XML 配置。你还可以用 Java configuration 来配置和使用 @AspectJ 。具体来说,你可以使用 @EnableLoadTimeWeaving 注解作为 <context:load-time-weaver/> 的替代品(有关详细信息,请参阅 below)。

以下示例展示了剖析方面,该方面很简单。它是一个基于时间的剖析器,它使用AspectJ 形式的方面声明:

  • Java

  • Kotlin

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(){}
}
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
class ProfilingAspect {

	@Around("methodsToBeProfiled()")
	fun profile(pjp: ProceedingJoinPoint): Any? {
		val sw = StopWatch(javaClass.simpleName)
		try {
			sw.start(pjp.getSignature().getName())
			return pjp.proceed()
		} finally {
			sw.stop()
			println(sw.prettyPrint())
		}
	}

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

我们还需要创建一个 META-INF/aop.xml 文件,以告知 AspectJ weaver,我们希望将 ProfilingAspect 编织到我们的类中。此文件约定,即在称为 META-INF/aop.xml 的 Java classpath 中存在一个文件(或多个文件),是标准的 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(稍后解释)。此装载时织入程序是负责将一个或多个 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>

现在,所有必需的产品(方面、 META-INF/aop.xml`文件和 Spring 配置)都已到位,我们可以创建一个带有 `main(..) 方法的以下驱动程序类来演示 LTW 的作用:

  • Java

  • Kotlin

// 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();
	}
}
// imports

fun main() {
	val ctx = ClassPathXmlApplicationContext("beans.xml")

	val service = ctx.getBean(EntitlementCalculationService.class)

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

我们还有一件事要做。本节的引言确实表示,可以基于每个ClassLoader 的基础通过 Spring 选择性地启动 LTW,这是正确的。但是,对于此示例,我们使用 Java 代理(随 Spring 提供)来启动 LTW。我们使用以下命令运行前面显示的Main 类:

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

-javaagent 是指定和启用 用于对在 JVM 上运行的程序进行仪器的 agent 的标志。Spring 框架附带这样一个 agent,即 InstrumentationSavingAgent,它包装在 @{26} 中,该包作为上述示例中 -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 程序的轻微变化产生相同结果:

  • Java

  • Kotlin

// 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();
	}
}
// imports

fun main(args: Array<String>) {
	ClassPathXmlApplicationContext("beans.xml")

	val service = StubEntitlementCalculationService()

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

注意,在前面的程序中,我们引导 Spring 容器,然后在 Spring 上下文外创建一个 StubEntitlementCalculationService 新实例。概要文件建议仍会编织进来。

不可否认的是,这个例子很简单。但是,Spring 中的 LTW 支持基础知识已在前面的示例中全面介绍,本节其余部分将详细说明配置和使用的每一部分背后的“原因”。

本示例中使用的 ProfilingAspect 可能比较基础,但非常有用。它是开发人员在开发过程中可以使用的开发期方面的良好示例,然后可以轻松地将其从部署到 UAT 或生产中的应用程序版本中排除。

Aspects

您在 LTW 中使用的方面必须是 AspectJ 方面。您可以使用 AspectJ 语言编写它们,也可以使用 @AspectJ 样式编写它们。然后,您的方面同时是有效的 AspectJ 和 Spring AOP 方面。此外,编译过的方面类需要在类路径上可用。

META-INF/aop.xml

AspectJ LTW 基础设施使用一个或多个 META-INF/aop.xml 文件进行配置,这些文件位于 Java 类路径上(直接或通常在 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 文件是 100% AspectJ,因此我们在此不再对其进行进一步说明。

Required libraries (JARS)

至少,您需要以下库才能使用 Spring Framework 对 AspectJ LTW 的支持:

  • spring-aop.jar

  • aspectjweaver.jar

如果您使用 Spring-provided agent to enable instrumentation,您还需要:

  • spring-instrument.jar

Spring Configuration

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

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

为特定 ApplicationContext 配置 LoadTimeWeaver 可能就像添加一行一样简单。(请注意,您几乎肯定需要使用 ApplicationContext 作为您的 Spring 容器——通常,BeanFactory 是不够的,因为 LTW 支持使用 BeanFactoryPostProcessors。)

要启用 Spring Framework 的 LTW 支持,您需要按如下方式配置 LoadTimeWeaver

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableLoadTimeWeaving
public class ApplicationConfiguration {
}
@Configuration
@EnableLoadTimeWeaving
class ApplicationConfiguration
<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">

	<context:load-time-weaver />

</beans>

前面的配置自动为 LTW 特定的基础设施 bean(例如 LoadTimeWeaverAspectJWeavingEnabler)进行定义和注册。默认的 LoadTimeWeaverDefaultContextLoadTimeWeaver 类,它尝试装饰自动检测到的 LoadTimeWeaver。“自动检测”的确切类型 LoadTimeWeaver 取决于您的运行时环境。下表总结了各种 LoadTimeWeaver 实现:

Table 1. DefaultContextLoadTimeWeaver LoadTimeWeavers
Runtime Environment LoadTimeWeaver implementation

Running in Apache Tomcat

TomcatLoadTimeWeaver

GlassFish中运行(仅限于 EAR 部署)

GlassFishLoadTimeWeaver

在 Red Hat’s JBoss ASWildFly中运行

JBossLoadTimeWeaver

JVM 使用 Spring InstrumentationSavingAgentjava -javaagent:path/to/spring-instrument.jar)启动

InstrumentationLoadTimeWeaver

回退,期望底层的 ClassLoader 遵循常见约定(即 addTransformer 和可选的 getThrowawayClassLoader 方法)

ReflectiveLoadTimeWeaver

请注意,当您使用 DefaultContextLoadTimeWeaver 时,该表只列出了自动检测到的 LoadTimeWeavers。您可以确切地指定要使用的 LoadTimeWeaver 实现。

要配置特定的 LoadTimeWeaver,实现 LoadTimeWeavingConfigurer 接口并覆盖 getLoadTimeWeaver() 方法(或使用 XML 等效项)。以下示例指定了 ReflectiveLoadTimeWeaver

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableLoadTimeWeaving
public class CustomWeaverConfiguration implements LoadTimeWeavingConfigurer {

	@Override
	public LoadTimeWeaver getLoadTimeWeaver() {
		return new ReflectiveLoadTimeWeaver();
	}
}
@Configuration
@EnableLoadTimeWeaving
class CustomWeaverConfiguration : LoadTimeWeavingConfigurer {

	override fun getLoadTimeWeaver(): LoadTimeWeaver {
		return ReflectiveLoadTimeWeaver()
	}
}
<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">

	<context:load-time-weaver
			weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>

</beans>

由配置定义和注册的 LoadTimeWeaver 可以通过使用众所周知的名称 loadTimeWeaver 从 Spring 容器中随后检索。请记住,LoadTimeWeaver 仅作为 Spring 的 LTW 基础设施用于添加一个或多个 ClassFileTransformers 的机制而存在。执行 LTW 的实际 ClassFileTransformerClassPreProcessorAgentAdapter(来自 org.aspectj.weaver.loadtime 包)类。请参见 ClassPreProcessorAgentAdapter 类的类级 javadoc 以获取更多详细信息,因为实际如何进行编织的具体细节超出了本文档的范围。

配置的最后一个属性还有待讨论:aspectjWeaving 属性(如果您使用 XML,则为 aspectj-weaving)。此属性控制是否启用 LTW。它接受三个可能的值之一,如果属性不存在,则默认值为 autodetect。下表总结了三个可能的值:

Table 2. AspectJ weaving attribute values
Annotation Value XML Value Explanation

ENABLED

on

AspectJ 织入已启用,并将方面在适当时机在加载时织入。

DISABLED

off

LTW 已关闭。不会在加载时织入任何方面。

AUTODETECT

autodetect

如果 Spring LTW 基础结构至少可以找到一个 META-INF/aop.xml 文件,则 AspectJ 织入已启用。否则,则已关闭。这是默认值。

Environment-specific Configuration

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

Tomcat, JBoss, WildFly

Tomcat 和 JBoss/WildFly 提供了一个通用应用程序 ClassLoader,它能够执行本地仪表化。Spring 的原生 LTW 可以利用那些 ClassLoader 实现来提供 AspectJ 编织。您可以简单地启用加载时编织,如 described earlier。具体来说,您不需要修改 JVM 启动脚本来添加 -javaagent:path/to/spring-instrument.jar

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

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

Generic Java Applications

当在不受特定 LoadTimeWeaver 实现支持的环境中需要类检测时,JVM 代理是通用的解决方案。对于这样的情况,Spring 提供 InstrumentationLoadTimeWeaver,它需要 Spring 特定的(但非常通用的)JVM 代理 spring-instrument.jar,这些代理可通过常见的 @EnableLoadTimeWeaving<context:load-time-weaver/> 设置自动检测。

要使用它,您需要向 Spring 代理提供以下 JVM 选项来启动虚拟机:

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

请注意,这需要修改 JVM 启动脚本,这可能会妨碍你在应用程序服务器环境中使用此功能(具体视你的服务器和你的操作策略而定)。也就是说,对于诸如独立 Spring Boot 应用程序的一应用每 JVM 部署,你通常在任何情况下都控制整个 JVM 设置。