在Spring应用程序中使用AspectJ

到目前为止,在本章中我们所涵盖的内容都是纯Spring AOP。在本节中,我们将看看如果您的需求超出了Spring AOP所提供的功能,您可以如何使用AspectJ编译器或织入器,而不是或者除了Spring AOP。

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

使用AspectJ在Spring中进行依赖注入领域对象

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

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

  • Java

  • Kotlin

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属性,如下例所示:

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

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

  • Java

  • Kotlin

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实例的定义。

您还可以使用自动装配来避免必须完全指定专用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对注解的存在起作用。实质上,该切面表示:“从带有@Configurable注解的类型的新对象的初始化返回后,根据注解的属性配置新创建的对象使用Spring”。在此上下文中,“初始化”指的是新实例化的对象(例如,使用new运算符实例化的对象)以及正在进行反序列化的Serializable对象(例如,通过readResolve())。

上述段落中的关键短语之一是“实质上”。对于大多数情况,“从新对象的初始化返回后”的确切语义是可以接受的。在此上下文中,“初始化后”意味着在构造对象后注入依赖项。这意味着依赖项在类的构造函数体中不可用。如果要求依赖项在构造函数体运行之前被注入,因此在构造函数体中可用,您需要在@Configurable声明中定义如下:

  • Java

  • Kotlin

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

您可以在AspectJ编程指南的附录中找到有关各种切入点类型的语言语义的更多信息

为使此功能正常工作,注释类型必须与AspectJ织入器进行织入。您可以使用构建时的Ant或Maven任务来执行此操作(例如,请参阅AspectJ开发环境指南)或加载时织入(请参阅Spring框架中的AspectJ加载时织入)。AnnotationBeanConfigurerAspect本身需要由Spring进行配置(以获取用于配置新对象的bean工厂的引用)。如果使用基于Java的配置,您可以将@EnableSpringConfigured添加到任何@Configuration类中,如下所示:

  • Java

  • Kotlin

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

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

<context:spring-configured/>

@Configurable对象在配置切面之前创建会导致在调试日志中发出消息,对象不会被配置。一个例子可能是Spring配置中的一个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。这样做会导致双重初始化,一次通过容器,一次通过切面。

单元测试@Configurable对象

@Configurable支持的目标之一是在没有硬编码查找困难的情况下实现对领域对象的独立单元测试。如果@Configurable类型未被AspectJ编织,那么在单元测试期间注解不起作用。您可以在测试对象中设置模拟或存根属性引用,然后像往常一样继续。如果@Configurable类型已被AspectJ编织,您仍然可以像往常一样在容器外进行单元测试,但每次构造@Configurable对象时会看到警告消息,指示它尚未被Spring配置。

使用多个应用程序上下文

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

考虑一个典型的Spring Web应用程序配置,其中有一个共享的父应用程序上下文,定义了通用业务服务、支持这些服务所需的一切,以及每个servlet的一个子应用程序上下文(其中包含特定于该servlet的定义)。所有这些上下文都存在于同一个ClassLoader层次结构中,因此AnnotationBeanConfigurerAspect只能引用其中一个。在这种情况下,我们建议在共享(父)应用程序上下文中定义@EnableSpringConfigured bean。这定义了您可能希望注入到领域对象中的服务。一个后果是,您不能使用@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方面,您可以使用它来驱动Spring的事务管理,用于带有@Transactional注解的类型和方法。这主要是为那些想要在Spring容器之外使用Spring框架的事务支持的用户而设计的。

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

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

类内方法上的@Transactional注解会覆盖类注解(如果存在)给出的默认事务语义。任何可见性的方法都可以被注解,包括私有方法。直接对非公共方法进行注解是获得这些方法执行事务划分的唯一方法。

自Spring Framework 4.2以来,spring-aspects提供了一个类似的方面,为标准的jakarta.transaction.Transactional注解提供完全相同的功能。查看JtaAnnotationTransactionAspect以获取更多详细信息。

对于希望使用Spring配置和事务管理支持但不想(或无法)使用注解的AspectJ程序员,spring-aspects.jar还包含您可以扩展以提供自己的切入点定义的abstract方面。查看AbstractBeanConfigurerAspectAbstractTransactionAspect方面的源代码以获取更多信息。例如,以下摘录显示了如何编写一个方面来配置通过匹配完全限定类名的原型bean定义定义的领域模型中对象的所有实例:

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

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

	// 创建新bean(领域模型中的任何对象)
	protected pointcut beanCreation(Object beanInstance) :
		initialization(new(..)) &&
		CommonPointcuts.inDomainModel() &&
		this(beanInstance);
}

使用Spring IoC配置AspectJ方面

在Spring应用程序中使用AspectJ方面时,自然希望能够配置这些方面。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 注意factory-method="aspectOf"属性

非单例方面更难配置。但是,通过创建原型bean定义并使用spring-aspects.jar中的@Configurable支持,可以在AspectJ运行时创建方面实例后配置这些方面实例。

如果您有一些@AspectJ方面希望与AspectJ编织(例如,为领域模型类型使用加载时编织)以及其他@AspectJ方面希望与Spring AOP一起使用,并且这些方面都在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运行时并未参与其中。

在Spring框架中使用AspectJ进行加载时织入

Load-time weaving (LTW) refers to the process of weaving AspectJ aspects into an application’s class files as they are being loaded into the Java virtual machine (JVM). The focus of this section is on configuring and using LTW in the specific context of the Spring Framework. This section is not a general introduction to LTW. For full details on the specifics of LTW and configuring LTW with only AspectJ (with Spring not being involved at all), see the LTW section of the AspectJ Development Environment Guide.

The value that the Spring Framework brings to AspectJ LTW is in enabling much finer-grained control over the weaving process. 'Vanilla' AspectJ LTW is effected by using a Java (5+) agent, which is switched on by specifying a VM argument when starting up a JVM. It is, thus, a JVM-wide setting, which may be fine in some situations but is often a little too coarse. Spring-enabled LTW lets you switch on LTW on a per-ClassLoader basis, which is more fine-grained and which can make more sense in a 'single-JVM-multiple-application' environment (such as is found in a typical application server environment).

Further, in certain environments, this support enables load-time weaving without making any modifications to the application server’s launch script that is needed to add -javaagent:path/to/aspectjweaver.jar or (as we describe later in this section) -javaagent:path/to/spring-instrument.jar. Developers configure the application context to enable load-time weaving instead of relying on administrators who typically are in charge of the deployment configuration, such as the launch script.

Now that the sales pitch is over, let us first walk through a quick example of AspectJ LTW that uses Spring, followed by detailed specifics about elements introduced in the example. For a complete example, see the Petclinic sample application.

第一个示例

假设您是一名应用程序开发人员,被要求诊断系统中一些性能问题的原因。我们将不使用性能分析工具,而是将打开一个简单的性能分析切面,让我们快速获得一些性能指标。然后我们可以立即在该特定区域应用更精细的性能分析工具。

这里介绍的示例使用XML配置。您也可以配置和使用@AspectJ与Java配置。具体来说,您可以使用@EnableLoadTimeWeaving注解作为<context:load-time-weaver/>的替代方法(有关详细信息,请参见下面的部分)。

以下示例显示了性能分析切面,不是很复杂。它是一个基于时间的性能分析器,使用@AspectJ风格的切面声明:

  • Java

  • Kotlin

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文件:

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

	<weaver>
		<!-- 仅编织我们应用程序特定包及其子包中的类 -->
		<include within="com.xyz..*"/>
	</weaver>

	<aspects>
		<!-- 仅编织此切面 -->
		<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">

	<!-- 一个服务对象;我们将对其方法进行性能分析 -->
	<bean id="entitlementCalculationService"
			class="com.xyz.StubEntitlementCalculationService"/>

	<!-- 这将打开加载时编织 -->
	<context:load-time-weaver/>
</beans>

现在,所有必需的构件(切面、META-INF/aop.xml文件和Spring配置)都就位了,我们可以创建以下带有main(..)方法的驱动程序类来演示LTW的工作原理:

  • Java

  • Kotlin

package com.xyz;

// 导入

public class Main {

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

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

		// 'woven'了性能分析切面的方法执行
		service.calculateEntitlement();
	}
}
package com.xyz

// 导入

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

	val service = ctx.getBean(EntitlementCalculationService.class)

	// 'woven'了性能分析切面的方法执行
	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,它打包在前面示例中作为-javaagent参数值提供的spring-instrument.jar中。

执行Main程序的输出如下示例所示。(我在calculateEntitlement()实现中引入了一个Thread.sleep(..)语句,以便性能分析器实际捕获到一些不是0毫秒的内容(01234毫秒不是AOP引入的开销)。以下清单显示了我们运行性能分析器时获得的输出:

计算权益

StopWatch 'ProfilingAspect': 运行时间(毫秒)= 1234
------ ----- ----------------------------
ms     %     任务名称
------ ----- ----------------------------
01234  100%  calculateEntitlement

由于这种LTW是通过使用完整的AspectJ实现的,我们不仅限于通知Spring bean。以下对Main程序的轻微变化产生相同结果:

  • Java

  • Kotlin

package com.xyz;

// 导入

public class Main {

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

		EntitlementCalculationService service =
				new StubEntitlementCalculationService();

		// 'woven'了性能分析切面的方法执行
		service.calculateEntitlement();
	}
}
package com.xyz

// 导入

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

	val service = StubEntitlementCalculationService()

	// 'woven'了性能分析切面的方法执行
	service.calculateEntitlement()
}

请注意,在上面的程序中,我们引导Spring容器,然后在Spring的上下文之外完全创建了一个新的StubEntitlementCalculationService实例。性能分析建议仍然被编织进去。

诚然,这个例子很简单。然而,Spring中LTW支持的基础在前面的例子中已经介绍了,本节的其余部分将详细解释每一部分配置和用法背后的原因。

在这个例子中使用的ProfilingAspect可能很基础,但它非常有用。这是一个很好的开发时切面的例子,开发人员可以在开发过程中使用,然后很容易地在部署到UAT或生产环境的应用构建中排除它。

切面

在LTW中使用的切面必须是AspectJ切面。您可以用AspectJ语言本身编写它们,也可以用@AspectJ风格编写您的切面。然后,您的切面既是有效的AspectJ切面,也是Spring AOP切面。此外,编译后的切面类需要在类路径上可用。

META-INF/aop.xml

使用一个或多个位于Java类路径上的META-INF/aop.xml文件配置AspectJ LTW基础设施(直接位于类路径上,或者更典型的是位于jar文件中)。例如:

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

	<weaver>
		<!-- 仅编织我们应用程序特定包及其子包中的类 -->
		<include within="com.xyz..*"/>
	</weaver>

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

此文件的结构和内容在AspectJ参考文档的LTW部分中有详细说明。由于aop.xml文件是100%的AspectJ,我们在这里不再详细描述。

所需的库(JAR包)

至少,您需要以下库才能使用Spring Framework对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,这为各种有趣的应用打开了大门,其中之一就是LTW的方面。

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

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

要启用Spring Framework的LTW支持,您需要配置一个LoadTimeWeaver,通常是通过使用@EnableLoadTimeWeaving注解来完成,如下所示:

  • Java

  • Kotlin

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

或者,如果您更喜欢基于XML的配置,请使用<context:load-time-weaver/>元素。请注意,该元素在context命名空间中定义。以下示例展示了如何使用<context:load-time-weaver/>

<?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类,它尝试装饰一个自动检测到的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实现。

要在Java配置中指定特定的LoadTimeWeaver,请实现LoadTimeWeavingConfigurer接口并重写getLoadTimeWeaver()方法。以下示例指定了一个ReflectiveLoadTimeWeaver

  • Java

  • Kotlin

@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属性的值。再次,以下示例指定了一个ReflectiveLoadTimeWeaver

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

通过配置定义和注册的LoadTimeWeaver可以通过使用众所周知的名称loadTimeWeaver从Spring容器中检索。请记住,LoadTimeWeaver仅作为Spring的LTW基础设施添加一个或多个ClassFileTransformers的机制存在。实际执行LTW的ClassFileTransformerClassPreProcessorAgentAdapter(来自org.aspectj.weaver.loadtime包)类。有关织入实际效果的具体细节,请参阅ClassPreProcessorAgentAdapter类的类级javadoc,因为织入的具体方式超出了本文档的范围。

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

表2. AspectJ编织属性值
注解值 XML值 解释

ENABLED

on

AspectJ编织已启用,并在适当时刻进行加载时编织方面。

DISABLED

off

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

AUTODETECT

autodetect

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

特定环境配置

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

Tomcat, JBoss, WildFly

Tomcat和JBoss/WildFly提供了一个通用的应用程序ClassLoader,可以进行本地仪器化。Spring的本机LTW可以利用这些ClassLoader实现来提供AspectJ编织。您可以简单地启用加载时编织,如前面所述。具体来说,您不需要修改JVM启动脚本以添加-javaagent:path/to/spring-instrument.jar

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

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

通用Java应用程序

在需要类仪器化的不受特定LoadTimeWeaver实现支持的环境中,JVM代理是一种通用解决方案。对于这种情况,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设置。