对于最新的稳定版本,请使用 Spring Framework 7.0.6!spring-doc.cadn.net.cn

在Spring应用程序中使用AspectJ

到目前为止,本章中我们所介绍的都是纯Spring AOP。在本节中,我们将探讨如何使用AspectJ编译器或织入器代替或补充Spring AOP,如果你的需求超出了Spring AOP本身提供的功能。spring-doc.cadn.net.cn

Spring 随带了一个小型的 AspectJ 方面库,该库在你的发行版中作为 spring-aspects.jar 单独提供。你需要将其添加到类路径中才能使用其中的方面。使用 AspectJ 与 Spring 进行依赖注入领域对象其他 Spring 方面用于 AspectJ 讨论了此库的内容以及如何使用它。通过 Spring IoC 配置 AspectJ 方面 讨论了如何使用 AspectJ 编译器编织的 AspectJ 方面进行依赖注入。在 Spring 框架中使用 AspectJ 进行加载时编织 提供了对使用 AspectJ 的 Spring 应用程序进行加载时编织的介绍。spring-doc.cadn.net.cn

使用AspectJ通过Spring实现领域对象的依赖注入

Spring 容器会实例化和配置在应用程序上下文中定义的 bean。还可以让 bean 工厂根据包含要应用的配置的 bean 定义名称来配置一个预存在的对象。 spring-aspects.jar 包含一个注解驱动的切面,该切面利用此功能允许对任何对象进行依赖注入。该支持旨在用于容器控制之外创建的对象。领域对象通常属于此类,因为它们通常通过 new 操作符或由 ORM 工具作为数据库查询的结果来编程创建。spring-doc.cadn.net.cn

The @Configurable 注解标记了一个类,使其有资格进行 Spring 驱动的配置。在最简单的情况下,你可以仅将其用作标记注解,如下例所示:spring-doc.cadn.net.cn

package com.xyz.domain;

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

@Configurable
public class Account {
	// ...
}
package com.xyz.domain

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

@Configurable
class Account {
	// ...
}

当用作标记接口时,Spring 会通过使用与完全限定类型名称相同的 bean 定义(通常是原型作用域)来配置该注解类型的实例(在这种情况下为 Account)。由于 bean 的默认名称是其类型的完全限定名称,因此声明原型定义的一种方便方法是省略 id 属性,如下例所示:spring-doc.cadn.net.cn

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

如果你想显式指定要使用的原型bean定义的名称,你可以在注解中直接指定,如下例所示:spring-doc.cadn.net.cn

package com.xyz.domain;

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

@Configurable("account")
public class Account {
	// ...
}
package com.xyz.domain

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

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

Spring 现在查找名为 account 的 bean 定义,并使用该定义来配置新的 Account 实例。spring-doc.cadn.net.cn

您还可以使用自动装配来避免必须指定专用的bean定义。要让Spring应用自动装配,请使用@Configurable注解的autowire属性。您可以指定@Configurable(autowire=Autowire.BY_TYPE)@Configurable(autowire=Autowire.BY_NAME),分别按类型或按名称进行自动装配。作为替代,更倾向于通过@Autowired@Inject在字段或方法级别显式地指定依赖注入,为您的@Configurablebeans指定注解驱动的依赖注入(有关详细信息,请参阅基于注解的容器配置)。您可以指定@Configurable(autowire=Autowire.BY_TYPE)@Configurable(autowire=Autowire.BY_NAME),分别按类型或按名称进行自动装配。对于您的@Configurablebeans,建议通过@Autowired@Inject指定明确的、注解驱动的依赖注入(请参阅基于注解的容器配置以获取更多详细信息)。spring-doc.cadn.net.cn

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

请注意,仅使用注解本身不会产生任何效果。真正起作用的是 AnnotationBeanConfigurerAspect 中的 spring-aspects.jar,它会响应注解的存在。本质上,该切面表示:“在初始化一个使用 @Configurable 注解的类型的对象返回后,根据该注解的属性,使用 Spring 配置新创建的对象”。在此上下文中,“初始化”既包括新实例化的对象(例如,通过 new 操作符实例化的对象),也包括正在进行反序列化的 Serializable 对象(例如,通过 readResolve() 方法)。spring-doc.cadn.net.cn

上述段落中的一个关键词是“本质上”。在大多数情况下,“在新对象初始化返回之后”的确切语义是合适的。在此上下文中,“初始化之后”意味着依赖项在对象被构造之后才被注入。这意味着在类的构造函数体内无法使用这些依赖项。如果你希望依赖项在构造函数体执行之前就被注入,从而可以在构造函数体内使用,则需要在 @Configurable 声明上进行如下定义:spring-doc.cadn.net.cn

@Configurable(preConstruction = true)
@Configurable(preConstruction = true)

关于各种切入点类型的语言语义的更多信息可以在AspectJ 在这个附录中找到,这是AspectJ编程指南的一部分。spring-doc.cadn.net.cn

要使这有效,注解的类型必须与AspectJ编织器进行编织。您可以使用构建时的Ant或Maven任务来完成此操作(例如,请参阅AspectJ开发环境指南),或者使用运行时编织(请参阅Spring框架中的AspectJ运行时编织)。AnnotationBeanConfigurerAspect本身需要由Spring进行配置(以便获取将用于配置新对象的bean工厂的引用)。如果您使用基于Java的配置,可以在任何@Configuration类中添加@EnableSpringConfigured,如下所示:spring-doc.cadn.net.cn

@Configuration
@EnableSpringConfigured
public class AppConfig {
}
@Configuration
@EnableSpringConfigured
class AppConfig {
}

如果您更喜欢基于XML的配置,Spring context 命名空间 定义了一个方便的 context:spring-configured 元素,您可以如下使用:spring-doc.cadn.net.cn

<context:spring-configured/>

在切面配置之前创建的@Configurable对象的实例会导致向调试日志发出消息,并且不会对对象进行配置。一个例子可能是Spring配置中的bean在被Spring初始化时创建领域对象。在这种情况下,可以使用depends-on bean属性手动指定bean依赖于配置切面。以下示例展示了如何使用depends-on属性:spring-doc.cadn.net.cn

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

	<!-- ... -->

</bean>
请勿通过bean配置器切面激活@Configurable处理,除非你确实打算在运行时依赖其语义。特别是,请确保不要在作为常规Spring bean向容器注册的bean类上使用@Configurable。这样做会导致双重初始化,一次通过容器,一次通过切面。

单元测试 @Configurable 对象

One of the goals of the @Configurable support is to enable independent unit testing of domain objects without the difficulties associated with hard-coded lookups. If @Configurable types have not been woven by AspectJ, the annotation has no affect during unit testing. You can set mock or stub property references in the object under test and proceed as normal. If @Configurable types have been woven by AspectJ, you can still unit test outside of the container as normal, but you see a warning message each time that you construct a @Configurable object indicating that it has not been configured by Spring.spring-doc.cadn.net.cn

与多个应用程序上下文一起工作

用于实现 @Configurable 支持的 AnnotationBeanConfigurerAspect 是一个AspectJ单例切面。单例切面的作用域与 static 成员的作用域相同:每个定义该类型的 ClassLoader 对应一个切面实例。这意味着,如果你在同一 ClassLoader 层次结构中定义了多个应用上下文,则需要考虑在何处定义 @EnableSpringConfigured Bean 以及将 spring-aspects.jar 放置在类路径中的什么位置。spring-doc.cadn.net.cn

考虑一个典型的Spring Web应用程序配置,该配置包含一个共享的父应用上下文,用于定义通用的业务服务、支持这些服务所需的一切,以及为每个Servlet设置的一个子应用上下文(其中包含特定于该Servlet的定义)。所有这些上下文在同一 ClassLoader 层次结构中并存,因此 AnnotationBeanConfigurerAspect 只能持有对其中一个上下文的引用。 在此情况下,我们建议在共享的(父级)应用上下文中定义 @EnableSpringConfigured bean。这可以定义你可能希望注入到领域对象中的服务。其结果是,你无法使用 @Configurable 机制将子上下文(Servlet特定)中定义的bean引用配置到领域对象中(不过这很可能也是你本来就不想做的事情)。spring-doc.cadn.net.cn

当在同一容器中部署多个Web应用程序时,请确保每个Web应用程序通过使用自己的 ClassLoader 来加载 spring-aspects.jar 中的类型(例如,通过将 spring-aspects.jar 放入 WEB-INF/lib 中)。如果仅将 spring-aspects.jar 添加到容器范围的类路径中(从而由共享的父级 ClassLoader 加载),则所有Web应用程序将共享同一个切面实例(这可能并非你所期望的结果)。spring-doc.cadn.net.cn

用于AspectJ的其他Spring方面

除了@Configurable方面,spring-aspects.jar包含一个AspectJ切面,你可以使用它来驱动Spring的事务管理,适用于带有@Transactional注解的类型和方法。这主要是为那些希望在Spring容器之外使用Spring框架的事务支持的用户设计的。spring-doc.cadn.net.cn

解释@Transactional注解的方面是AnnotationTransactionAspect。当你使用这个方面时,你必须注解实现类(或该类中的方法或两者),而不是该类实现的接口(如果有的话)。AspectJ遵循Java的规则,即接口上的注解不会被继承。spring-doc.cadn.net.cn

一个 @Transactional 注解在类上指定了该类中任何公共操作执行的默认事务语义。spring-doc.cadn.net.cn

一个 @Transactional 注解在类中的方法上会覆盖由类注解(如果存在)给出的默认事务语义。任何可见性的方法都可以被注解,包括私有方法。直接注解非公共方法是为执行此类方法获取事务边界划分的唯一方式。spring-doc.cadn.net.cn

自 Spring Framework 4.2 起,spring-aspects 提供了一个类似的功能,为标准的 jakarta.transaction.Transactional 注解提供了完全相同的功能。有关更多详细信息,请参阅 JtaAnnotationTransactionAspect

对于AspectJ程序员,如果想要使用Spring配置和事务管理支持但不想(或不能)使用注解,spring-aspects.jar还包含abstract个你可以扩展的切面来提供自己的切入点定义。有关AbstractBeanConfigurerAspectAbstractTransactionAspect切面的更多信息,请参阅源代码。作为一个示例,以下摘录展示了如何编写一个切面来配置所有通过匹配完全限定类名的原型bean定义在领域模型中定义的对象实例:spring-doc.cadn.net.cn

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切面时,很自然地希望并且期望能够通过Spring配置这些切面。AspectJ运行时本身负责切面的创建,而通过Spring配置AspectJ创建的切面的方式取决于AspectJ实例化模型(切面使用的per-xxx子句)。spring-doc.cadn.net.cn

大多数AspectJ切面是单例切面。配置这些切面很容易。你可以创建一个引用切面类型的bean定义,并包含factory-method="aspectOf" bean属性。这确保了Spring通过询问AspectJ来获取切面实例,而不是尝试自己创建实例。以下示例展示了如何使用factory-method="aspectOf"属性:spring-doc.cadn.net.cn

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

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

非单例切面更难配置。但是,可以通过创建原型bean定义并使用@Configurablespring-aspects.jar提供的支持来配置切面实例,一旦它们被AspectJ运行时创建。spring-doc.cadn.net.cn

如果你有一些@AspectJ切面,你希望使用AspectJ进行编织(例如,使用加载时编织对领域模型类型),以及其他@AspectJ切面,你希望与Spring AOP一起使用,并且这些切面都在Spring中配置,你需要告诉Spring AOP @AspectJ自动代理支持应该使用配置中定义的哪些确切子集的@AspectJ切面用于自动代理。你可以通过在<aop:aspectj-autoproxy/>声明中使用一个或多个<include/>元素来实现这一点。每个<include/>元素指定一个名称模式,只有名称匹配至少一个模式的bean才会被用于Spring AOP自动代理配置。以下示例展示了如何使用<include/>元素:spring-doc.cadn.net.cn

<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 的切面编织到这些类文件中的过程。本节的重点是在 Spring 框架的具体上下文中配置和使用 LTW。本节不是 LTW 的一般介绍。有关 LTW 的详细信息以及仅使用 AspectJ 配置 LTW(与 Spring 无关)的详细信息,请参阅 AspectJ 开发环境指南中关于 LTW 的部分spring-doc.cadn.net.cn

Spring框架为AspectJ LTW带来的价值在于能够更精细地控制编织过程。'Vanilla' AspectJ LTW是通过使用Java(5+)代理实现的,该代理在启动JVM时通过指定一个虚拟机参数来启用。因此,它是一个全局设置,这在某些情况下可能没问题,但在很多情况下有点过于粗犷。Spring启用的LTW允许你以每个ClassLoader的基础上切换LTW,这是更精细粒度的,并且在“单个JVM-多个应用程序”的环境中更有意义(例如在典型的服务器环境中)。spring-doc.cadn.net.cn

Further, 在某些环境中, 这种支持可以在不修改应用程序服务器启动脚本的情况下启用加载时编织,而该启动脚本通常需要添加-javaagent:path/to/aspectjweaver.jar或(如我们在本节后面描述的)-javaagent:path/to/spring-instrument.jar。开发人员配置应用程序上下文以启用加载时编织,而不是依赖于通常负责部署配置(例如启动脚本)的管理员。spring-doc.cadn.net.cn

现在销售介绍已经结束,让我们首先快速浏览一个使用Spring的AspectJ LTW示例,然后详细介绍示例中引入的元素。有关完整示例,请参阅 宠物诊所示例应用spring-doc.cadn.net.cn

第一个示例

假设你是一名应用程序开发人员,被要求诊断系统中某些性能问题的原因。我们不打算使用性能分析工具,而是要启用一个简单的性能分析切面,这样我们可以快速获得一些性能指标。然后,我们可以立即对该特定区域应用更精细的性能分析工具。spring-doc.cadn.net.cn

这里提供的示例使用了XML配置。你也可以使用Java配置来配置和使用@AspectJ。具体来说,你可以使用@EnableLoadTimeWeaving注解作为<context:load-time-weaver/>的替代(请参见下方的详细信息)。

下面的示例展示了配置文件方面的内容,这并不花哨。 它是一个基于时间的配置文件,使用了@AspectJ风格的切面声明:spring-doc.cadn.net.cn

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(){}
}
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
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编织器我们希望将我们的 ProfilingAspect 编织到类中。这个文件约定,即在Java类路径上存在一个名为 META-INF/aop.xml 的文件(或文件),是标准的AspectJ做法。下面的例子展示了 aop.xml 文件:spring-doc.cadn.net.cn

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

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

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

</aspectj>

现在我们可以继续进行Spring特定的配置部分。我们需要配置一个LoadTimeWeaver(稍后解释)。这个加载时织入器是负责将一个或多个META-INF/aop.xml文件中的切面配置织入到应用程序类中的关键组件。好处是它不需要很多配置(有一些更多的选项可以指定,但这些将在稍后详细介绍),如以下示例所示:spring-doc.cadn.net.cn

<?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的实际效果:spring-doc.cadn.net.cn

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();
	}
}
package com.xyz

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

我们还有一件事要做。本节的介绍确实提到可以使用Spring在每个ClassLoader的基础上有选择地开启LTW,这是正确的。 但是,在这个示例中,我们使用一个Java代理(随Spring提供)来开启LTW。 我们使用以下命令运行前面显示的Main类:spring-doc.cadn.net.cn

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

The -javaagent 是一个标志,用于指定和启用 代理程序 以对在 JVM 上运行的程序进行插桩。Spring 框架附带了一个这样的 代理程序,即 InstrumentationSavingAgent,它被包含在 spring-instrument.jar 中,该文件是在前面的例子中作为 -javaagent 参数的值提供的。spring-doc.cadn.net.cn

执行Main程序的输出看起来类似于下一个示例。 (我在calculateEntitlement()实现中引入了一个Thread.sleep(..)语句,以便配置文件实际上捕获到除0毫秒以外的内容 (01234毫秒并不是由AOP引入的开销)。 以下列表显示了我们运行配置文件时得到的输出:spring-doc.cadn.net.cn

Calculating entitlement

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

由于这个LTW是通过使用完整的AspectJ实现的,我们不仅限于建议Spring bean。以下对Main程序的轻微修改会得到相同的结果:spring-doc.cadn.net.cn

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();
	}
}
package com.xyz

// 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-doc.cadn.net.cn

诚然,这个例子过于简单。然而,前面的例子已经介绍了 Spring 中 LTW 支持的所有基础知识,本节的其余部分将详细解释每项配置和用法背后的原因。spring-doc.cadn.net.cn

The ProfilingAspect 在这个示例中使用的基本,但它非常有用。这是一个很好的开发时特性的例子,开发人员可以在开发过程中使用它,然后在将应用程序部署到UAT或生产环境时轻松排除。

面向切面

你在LTW中使用的切面必须是AspectJ切面。你可以用AspectJ语言本身来编写它们,也可以用@AspectJ风格来编写你的切面。这样你的切面既是有效的AspectJ切面,也是Spring AOP切面。此外,编译后的切面类需要在类路径上可用。spring-doc.cadn.net.cn

'META-INF/aop.xml'

AspectJ LTW基础设施通过使用一个或多个META-INF/aop.xml 文件进行配置,这些文件位于Java类路径上(直接或更典型地在jar文件中)。spring-doc.cadn.net.cn

此文件的结构和内容在AspectJ参考文档的LTW部分有详细说明。因为aop.xml文件是100%的AspectJ,我们在这里不再进一步描述它。spring-doc.cadn.net.cn

所需库(JAR)

至少,你需要以下库来使用Spring框架对AspectJ LTW的支持:spring-doc.cadn.net.cn

Spring 配置

Spring 的 LTW 支持中的关键组件是 LoadTimeWeaver 接口(在 org.springframework.instrument.classloading 包中),以及 Spring 发行版中附带的该接口的众多实现。LoadTimeWeaver 负责在运行时向 ClassLoader 添加一个或多个 java.lang.instrument.ClassFileTransformers,这为各种有趣的应用打开了大门,其中之一就是 LTW 方面。spring-doc.cadn.net.cn

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

为特定的 ApplicationContext 配置一个 LoadTimeWeaver 可以像添加一行代码一样简单。(请注意,您几乎肯定需要使用一个 ApplicationContext 作为您的 Spring 容器——通常,BeanFactory 是不够的,因为 LTW 支持使用 BeanFactoryPostProcessors。)spring-doc.cadn.net.cn

要启用Spring框架的LTW支持,您需要配置一个LoadTimeWeaver, 这通常是通过使用@EnableLoadTimeWeaving注解来完成的,如下所示:spring-doc.cadn.net.cn

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}

Alternatively, if you prefer XML-based configuration, use the <context:load-time-weaver/> element. Note that the element is defined in the context namespace. The following example shows how to use <context:load-time-weaver/>:spring-doc.cadn.net.cn

<?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">

	<context:load-time-weaver/>

</beans>

上述配置会自动定义并注册一些特定于 LTW 的基础架构 Bean,例如 LoadTimeWeaverAspectJWeavingEnabler。默认的 LoadTimeWeaverDefaultContextLoadTimeWeaver 类,它会尝试装饰一个自动检测到的 LoadTimeWeaverLoadTimeWeaver 所“自动检测”到的具体类型取决于您的运行环境。下表总结了各种 LoadTimeWeaver 实现:spring-doc.cadn.net.cn

表 1. DefaultContextLoadTimeWeaver 的 LoadTimeWeavers
运行时环境 LoadTimeWeaver 实现

Apache Tomcat 中运行spring-doc.cadn.net.cn

TomcatLoadTimeWeaverspring-doc.cadn.net.cn

GlassFish中运行(仅限于EAR部署)spring-doc.cadn.net.cn

GlassFishLoadTimeWeaverspring-doc.cadn.net.cn

在Red Hat的JBoss ASWildFly中运行spring-doc.cadn.net.cn

JBossLoadTimeWeaverspring-doc.cadn.net.cn

JVM 已使用 Spring InstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar)spring-doc.cadn.net.cn

InstrumentationLoadTimeWeaverspring-doc.cadn.net.cn

Fallback,期望底层的ClassLoader遵循常见约定 (即addTransformer和可选的getThrowawayClassLoader方法)spring-doc.cadn.net.cn

ReflectiveLoadTimeWeaverspring-doc.cadn.net.cn

请注意,该表仅列出在您使用DefaultContextLoadTimeWeaver时自动检测到的LoadTimeWeavers。您可以指定要使用的LoadTimeWeaver实现。spring-doc.cadn.net.cn

要使用Java配置指定一个特定的LoadTimeWeaver,实现LoadTimeWeavingConfigurer接口并重写getLoadTimeWeaver()方法。 以下示例指定了一个ReflectiveLoadTimeWeaverspring-doc.cadn.net.cn

@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {

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

	override fun getLoadTimeWeaver(): LoadTimeWeaver {
		return ReflectiveLoadTimeWeaver()
	}
}

如果您使用基于XML的配置,您可以将完全限定的类名指定为<context:load-time-weaver/>元素上的weaver-class属性的值。同样,以下示例指定了一个ReflectiveLoadTimeWeaverspring-doc.cadn.net.cn

<?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">

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

</beans>

The LoadTimeWeaver 由配置定义并注册,可以在稍后使用众所周知的名称 loadTimeWeaver 从 Spring 容器中检索。 请记住,LoadTimeWeaver 仅作为 Spring 的 LTW 基础设施添加一个或多个 ClassFileTransformers 的机制存在。实际执行 LTW 的是 ClassFileTransformer(来自 org.aspectj.weaver.loadtime 包)类。ClassPreProcessorAgentAdapter 类。有关详细信息,请参阅 ClassPreProcessorAgentAdapter 类的类级 javadoc,因为具体的编织方式超出了本文档的范围。spring-doc.cadn.net.cn

有一个配置属性尚未讨论:aspectjWeaving 属性(或aspectj-weaving 如果你使用 XML)。这个属性控制 LTW 是否启用。它接受三个可能的值之一,默认值为 autodetect 如果属性不存在。下表总结了这三个 可能的值:spring-doc.cadn.net.cn

表 2. AspectJ 织入属性值
注解值 XML 值 说明

ENABLEDspring-doc.cadn.net.cn

onspring-doc.cadn.net.cn

AspectJ 织入已开启,并且在加载时根据需要织入切面。spring-doc.cadn.net.cn

DISABLEDspring-doc.cadn.net.cn

offspring-doc.cadn.net.cn

LTW 已关闭。加载时没有编织任何切面。spring-doc.cadn.net.cn

AUTODETECTspring-doc.cadn.net.cn

autodetectspring-doc.cadn.net.cn

如果Spring LTW基础设施可以找到至少一个META-INF/aop.xml文件, 则AspectJ编织是开启的。否则,它是关闭的。这是默认值。spring-doc.cadn.net.cn

特定于环境的配置

这一部分包含了在使用Spring的LTW支持时所需的任何额外设置和配置,特别是在应用服务器和Web容器等环境中。spring-doc.cadn.net.cn

Tomcat、JBoss、WildFly

Tomcat 和 JBoss/WildFly 提供了一个通用的应用程序 ClassLoader,能够实现本地化检测。Spring 的原生 LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 织入功能。 你可以直接启用加载时织入,如前面所述。 具体来说,你无需修改 JVM 启动脚本来添加 -javaagent:path/to/spring-instrument.jarspring-doc.cadn.net.cn

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

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

通用Java应用程序

当需要在不受特定LoadTimeWeaver实现支持的环境中进行类级仪器化时,JVM代理是一般解决方案。对于此类情况,Spring提供了InstrumentationLoadTimeWeaver,它需要一个Spring特有的(但非常通用的)JVM代理spring-instrument.jar,该代理可被常见的@EnableLoadTimeWeaving<context:load-time-weaver/>设置自动检测。spring-doc.cadn.net.cn

要使用它,您必须通过提供以下JVM选项来启动带有Spring代理的虚拟机:spring-doc.cadn.net.cn

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

请注意,这需要修改JVM启动脚本,这可能会阻止你在应用服务器环境中使用此功能(取决于你的服务器和操作策略)。也就是说,对于单个JVM部署一个应用程序的情况,例如独立的Spring Boot应用程序,你通常无论如何都会控制整个JVM设置。spring-doc.cadn.net.cn