到目前为止,我们在本章中介绍的所有内容都是纯粹的 Spring AOP。在本节中, 我们将了解如何使用 AspectJ 编译器或 weaver 而不是 or in 除了 Spring AOP 之外,如果您的需求超出了 Spring AOP 提供的功能 独自。

Spring 附带了一个小型 AspectJ 方面库,该库可在 分布为 .您需要按顺序将其添加到类路径中 以使用其中的方面。使用 AspectJ 使用 Spring 和 AspectJ 的其他 Spring 方面进行依赖注入域对象,讨论 此库的内容以及如何使用它。使用 Spring IoC 配置 AspectJ Aspects 讨论了如何 依赖项注入使用 AspectJ 编译器编织的 AspectJ 方面。最后,Spring 框架中使用 AspectJ 的加载时间编织介绍了 Spring 应用程序的加载时间编织 使用 AspectJ。spring-aspects.jar

使用 AspectJ 依赖注入 Spring 的域对象

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

注释将类标记为符合 Spring 驱动的条件 配置。在最简单的情况下,您可以将其纯粹用作标记注释,因为 以下示例显示:@Configurable

  • 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 定义(通常 prototype-scoped) 与完全限定类型名称同名 ().由于 Bean 的默认名称是 其类型的完全限定名称,这是声明原型定义的便捷方式 省略该属性,如以下示例所示:Accountcom.xyz.domain.Accountid

<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 现在查找一个名为 Bean 的定义,并将其用作 定义以配置新实例。accountAccount

您还可以使用自动接线来避免在以下位置指定专用 Bean 定义 都。要让 Spring 应用自动接线,请使用注释的属性。您可以按类型或名称指定 or 用于自动接线, 分别。作为替代方法,最好指定显式的、注释驱动的 通过字段或方法级别或在字段或方法级别对 Bean 进行依赖注入(有关详细信息,请参阅基于注释的容器配置)。autowire@Configurable@Configurable(autowire=Autowire.BY_TYPE)@Configurable(autowire=Autowire.BY_NAME)@Configurable@Autowired@Inject

最后,您可以为新 使用属性创建和配置对象(例如,)。如果此属性是 设置为 ,Spring 在配置后验证所有属性( 不是基元或集合)已设置。dependencyCheck@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)true

请注意,单独使用注释不会执行任何操作。它是作用于存在 注释。从本质上讲,该方面说,“从初始化返回后 一个类型的新对象,配置新创建的对象 根据注释的属性使用 Spring“。在这种情况下, “初始化”是指新实例化的对象(例如,实例化的对象 与操作员)以及正在经历的对象 反序列化(例如,通过 readResolve())。AnnotationBeanConfigurerAspectspring-aspects.jar@ConfigurablenewSerializable

上一段中的一个关键词是“实质上”。在大多数情况下, “从新对象的初始化返回后”的确切语义是 好。在此上下文中,“初始化后”意味着依赖项是 在对象构建完成后注入。这意味着依赖项 不可用于类的构造函数主体。如果想要 在构造函数主体运行之前注入的依赖项,因此是 可用于构造函数的正文,您需要在声明中定义它,如下所示:@Configurable

  • Java

  • Kotlin

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

您可以找到有关各种切入点的语言语义的详细信息 在《AspectJ 编程指南》的附录中键入 AspectJ。

为此,必须使用 AspectJ 织布机编织带注释的类型。您可以 使用构建时 Ant 或 Maven 任务来执行此操作(例如,参见 AspectJ 开发 环境指南)或加载时编织(请参阅 Spring 框架中使用 AspectJ 的加载时编织)。本身需要通过 Spring 进行配置(为了获得 对用于配置新对象的 Bean 工厂的引用)。如果你 使用基于 Java 的配置,可以添加到任何类中,如下所示:AnnotationBeanConfigurerAspect@EnableSpringConfigured@Configuration

  • Java

  • Kotlin

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

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

<context:spring-configured/>

在配置方面之前创建的对象的实例 导致向调试日志发出一条消息,并且没有配置 对象正在发生。一个示例可能是 Spring 配置中的 bean,它创建 domain 对象,当它被 Spring 初始化时。在这种情况下,您可以使用 bean 属性手动指定 Bean 依赖于 配置方面。下面的示例演示如何使用该属性:@Configurabledepends-ondepends-on

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

	<!-- ... -->

</bean>
不要通过 bean configurer 方面激活处理,除非您 真的意味着在运行时依赖它的语义。特别是,请确保您这样做 不适用于注册为常规 Spring Bean 的 Bean 类 与容器。这样做会导致双重初始化,一次通过 容器和一次通过方面。@Configurable@Configurable

单元测试对象@Configurable

支持的目标之一是启用独立的单元测试 域对象,而没有与硬编码查找相关的困难。 如果类型不是由 AspectJ 编织的,则注释没有影响 在单元测试期间。您可以在 测试并照常进行。如果类型是由 AspectJ 编织的, 您仍然可以像往常一样在容器外部进行单元测试,但您会看到一条警告 每次构造对象时的消息,指示它具有 Spring 未配置。@Configurable@Configurable@Configurable@Configurable

使用多个应用程序上下文

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

考虑具有共享父应用程序的典型 Spring Web 应用程序配置 定义常见业务服务的上下文,支持这些服务所需的一切, 以及每个 Servlet 的一个子应用程序上下文(其中包含特定的定义 到该 servlet)。所有这些上下文都共存于同一层次结构中, 因此,只能保留对其中一个的引用。 在这种情况下,我们建议在共享中定义 Bean (父)应用程序上下文。这定义了您可能想要的服务 注入到域对象中。结果是无法配置域对象 引用在子(特定于 servlet)上下文中定义的 Bean,方法是使用 @Configurable机制(这可能不是你想做的事情)。ClassLoaderAnnotationBeanConfigurerAspect@EnableSpringConfigured

在同一容器中部署多个 Web 应用程序时,请确保每个 Web 应用程序使用自己的类型加载类型(例如,通过放置 )。如果仅添加到容器范围的类路径(因此由共享父级加载),则所有 Web 应用程序共享相同的方面实例(可能是 不是你想要的)。spring-aspects.jarClassLoaderspring-aspects.jarWEB-INF/libspring-aspects.jarClassLoader

上一段中的一个关键词是“实质上”。在大多数情况下, “从新对象的初始化返回后”的确切语义是 好。在此上下文中,“初始化后”意味着依赖项是 在对象构建完成后注入。这意味着依赖项 不可用于类的构造函数主体。如果想要 在构造函数主体运行之前注入的依赖项,因此是 可用于构造函数的正文,您需要在声明中定义它,如下所示:@Configurable

  • Java

  • Kotlin

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

您可以找到有关各种切入点的语言语义的详细信息 在《AspectJ 编程指南》的附录中键入 AspectJ。

不要通过 bean configurer 方面激活处理,除非您 真的意味着在运行时依赖它的语义。特别是,请确保您这样做 不适用于注册为常规 Spring Bean 的 Bean 类 与容器。这样做会导致双重初始化,一次通过 容器和一次通过方面。@Configurable@Configurable

AspectJ 的其他 Spring 方面

除了 aspect 之外,还包含一个 AspectJ 可用于驱动 Spring 的类型和方法事务管理的方面 用注释进行注释。这主要适用于以下用户 想要在 Spring 容器之外使用 Spring Framework 的事务支持。@Configurablespring-aspects.jar@Transactional

解释注释的方面是 。当你使用这个方面时,你必须注释 实现类(或该类中的方法或两者),而不是接口(如果 any) 实现。AspectJ 遵循 Java 的规则,即对 接口不会被继承。@TransactionalAnnotationTransactionAspect

类的注释指定了 类中任何公共操作的执行。@Transactional

类中方法的注释将覆盖缺省值 由类注解(如果存在)给出的事务语义。任何方法 可以对可见性进行注释,包括私有方法。注释非公共方法 直接是获取交易划分以执行此类方法的唯一方法。@Transactional

由于 Spring Framework 4.2 提供了一个类似的方面,它提供了 与标准注释完全相同的功能。查看更多详细信息。spring-aspectsjakarta.transaction.TransactionalJtaAnnotationTransactionAspect

对于想要使用 Spring 配置和事务的 AspectJ 程序员 管理支持,但不想(或不能)使用注释,还包含可以扩展以提供自己的切入点的方面 定义。有关详细信息,请参阅 和 方面的源代码。例如,以下内容 摘录显示了如何编写一个方面来配置对象的所有实例 在领域模型中使用与 完全限定的类名:spring-aspects.jarabstractAbstractBeanConfigurerAspectAbstractTransactionAspect

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 Framework 4.2 提供了一个类似的方面,它提供了 与标准注释完全相同的功能。查看更多详细信息。spring-aspectsjakarta.transaction.TransactionalJtaAnnotationTransactionAspect

使用 Spring IoC 配置 AspectJ Aspects

当您在 Spring 应用程序中使用 AspectJ 方面时,很自然地想要和 期望能够使用 Spring 配置这些方面。AspectJ 运行时本身是 负责方面创建,以及配置 AspectJ 创建的方法 通过 Spring 的方面取决于 AspectJ 实例化模型(子句) 由方面使用。per-xxx

大多数 AspectJ 方面都是单例方面。这些的配置 方面很容易。您可以创建一个 Bean 定义,该定义将 aspect 类型引用为 normal 并包含 bean 属性。这确保了 Spring 通过向 AspectJ 请求 aspect 实例来获取它,而不是尝试创建 实例本身。下面的示例演示如何使用该属性:factory-method="aspectOf"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 定义,并在 ASPECT 实例创建 Bean 后,使用 FROM 支持来配置 ASPECT 实例 AspectJ 运行时。@Configurablespring-aspects.jar

如果您有一些@AspectJ方面想要与 AspectJ 一起编织(例如, 对域模型类型使用加载时编织)以及所需的其他@AspectJ方面 与Spring AOP一起使用,而这些方面都是在Spring中配置的,你 需要告诉 Spring AOP @AspectJ 自动代理支持哪个确切的子集 配置中定义@AspectJ方面应用于自动代理。您可以 为此,请在声明中使用一个或多个元素。每个元素都指定一个名称模式,并且只有 bean 与至少一种模式匹配的名称用于 Spring AOP 自动代理 配置。下面的示例演示如何使用元素:<include/><aop:aspectj-autoproxy/><include/><include/>

<aop:aspectj-autoproxy>
	<aop:include name="thisBean"/>
	<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
不要被元素的名称所误导。使用它 导致创建 Spring AOP 代理。@AspectJ风格的方面 这里使用声明,但不涉及 AspectJ 运行时。<aop:aspectj-autoproxy/>
1 注意属性factory-method="aspectOf"
不要被元素的名称所误导。使用它 导致创建 Spring AOP 代理。@AspectJ风格的方面 这里使用声明,但不涉及 AspectJ 运行时。<aop:aspectj-autoproxy/>

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

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

Spring 框架为 AspectJ LTW 带来的价值在于实现很多 对织造过程进行更精细的控制。'Vanilla' AspectJ LTW 通过使用 Java (5+) 代理,通过在启动 JVM。因此,它是 JVM 范围的设置,在某些情况下可能很好,但通常是 有点太粗糙了。启用 Spring 的 LTW 允许您在 每个基数,哪个更细粒度,哪个可以做得更多 在“单 JVM-多应用程序”环境中(例如在典型的 应用程序服务器环境)。ClassLoader

此外,在某些环境中,这种支持可以 加载时编织,无需对应用程序服务器的启动进行任何修改 添加 OR 所需的脚本(如我们所述 本节后面)。开发人员配置 用于启用加载时编织而不是依赖管理员的应用程序上下文 谁通常负责部署配置,例如启动脚本。-javaagent:path/to/aspectjweaver.jar-javaagent:path/to/spring-instrument.jar

现在推销已经结束,让我们先来看看 AspectJ 的一个快速示例 使用 Spring 的 LTW,然后是有关 例。有关完整示例,请参阅 Petclinic 示例应用程序。

第一个例子

假设您是一名应用程序开发人员,其任务是诊断 系统中某些性能问题的原因。与其爆发一个 分析工具,我们将打开一个简单的分析方面,让我们 快速获取一些性能指标。然后,我们可以应用更细粒度的分析 工具之后立即到该特定区域。

此处提供的示例使用 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() {
	}
}

我们还需要创建一个文件,以通知 AspectJ weaver 我们想把我们的融入到我们的课堂上。本文件约定,即 Java 类路径上存在一个名为 标准方面J。以下示例显示了该文件:META-INF/aop.xmlProfilingAspectMETA-INF/aop.xmlaop.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>
建议只编织特定的类(通常是 应用程序包,如上面的示例所示) 以避免 AspectJ 转储文件和警告等副作用。 从效率的角度来看,这也是一种最佳实践。aop.xml

现在,我们可以继续讨论配置中特定于 Spring 的部分。我们需要 配置 A(稍后解释)。这个加载时织机是 负责将方面配置编织成一个或 将更多文件添加到应用程序的类中。好的 问题是它不需要很多配置(还有更多 您可以指定的选项,但这些选项将在后面详细介绍),如 以下示例:LoadTimeWeaverMETA-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>

现在,所有必需的工件(方面、文件和 Spring 配置)都已就绪,我们可以创建以下内容 驱动程序类,其中包含演示 LTW 实际操作的方法:META-INF/aop.xmlmain(..)

  • Java

  • Kotlin

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 有选择地打开 LTW,这是真的。 但是,在此示例中,我们使用 Java 代理(随 Spring 提供)来打开 LTW。 我们使用以下命令来运行前面所示的类:ClassLoaderMain

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

是用于指定和启用代理的标志 检测在 JVM 上运行的程序。Spring 框架附带了这样的 agent,它被打包在 that 中,作为 前面的示例。-javaagentInstrumentationSavingAgentspring-instrument.jar-javaagent

程序执行的输出类似于下一个示例。 (我在实现中引入了一个语句,以便探查器实际捕获 0 以外的内容 毫秒(毫秒不是 AOP 引入的开销)。 以下列表显示了我们在运行探查器时得到的输出:MainThread.sleep(..)calculateEntitlement()01234

Calculating entitlement

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

由于此 LTW 是通过使用成熟的 AspectJ 来实现的,因此我们不仅限于提供建议 春豆。以下程序的细微变化会产生相同的结果 结果:Main

  • Java

  • Kotlin

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 中 LTW 支持的基础知识 在前面的示例中都介绍了这些内容,本节的其余部分将进行说明 详细介绍每个配置和用法背后的“原因”。

此示例中使用的可能是基本的,但它非常有用。这是一个 开发人员在开发过程中可以使用的开发时方面的一个很好的例子 然后轻松地从正在部署的应用程序的构建中排除 进入UAT或生产。ProfilingAspect

方面

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

META-INF/aop.xml

AspectJ LTW 基础结构是通过使用 Java 类路径上的一个或多个文件(直接或更典型的 jar 文件)来配置的。 例如:META-INF/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>

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

此文件的结构和内容在 AspectJ 参考的 LTW 部分进行了详细介绍 文档。由于该文件是 100% AspectJ,因此我们在这里不再进一步描述。aop.xml

必需的库 (JARS)

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

  • spring-aop.jar

  • aspectjweaver.jar

如果使用 Spring 提供的代理来启用检测,则还需要:

  • spring-instrument.jar

Spring 配置

Spring 的 LTW 支持的关键组件是接口(在包中)和众多的实现 其中随 Spring 发行版一起发布。A 负责 向 AT 添加一个或多个 运行时,它为各种有趣的应用程序打开了大门,其中之一 恰好是方面的 LTW。LoadTimeWeaverorg.springframework.instrument.classloadingLoadTimeWeaverjava.lang.instrument.ClassFileTransformersClassLoader

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

为特定配置可以像 添加一行。(请注意,您几乎肯定需要使用 an 作为 Spring 容器——通常,a 不是 足够了,因为 LTW 支持使用 .)LoadTimeWeaverApplicationContextApplicationContextBeanFactoryBeanFactoryPostProcessors

要启用 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,例如 A 和 An 。 默认值是类,它尝试 装饰自动检测到的 .“自动检测”的确切类型取决于您的运行时环境。 下表总结了各种实现:LoadTimeWeaverAspectJWeavingEnablerLoadTimeWeaverDefaultContextLoadTimeWeaverLoadTimeWeaverLoadTimeWeaverLoadTimeWeaver

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

在 Apache Tomcat 中运行

TomcatLoadTimeWeaver

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

GlassFishLoadTimeWeaver

在 Red Hat 的 JBoss AS 或 WildFly 中运行

JBossLoadTimeWeaver

JVM 从 Spring 开始InstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar)

InstrumentationLoadTimeWeaver

回退,期望基础 ClassLoader 遵循常见约定 (即和可选的方法)addTransformergetThrowawayClassLoader

ReflectiveLoadTimeWeaver

请注意,该表仅列出了以下情况下自动检测到的 使用 .您可以准确指定要使用的实现。LoadTimeWeaversDefaultContextLoadTimeWeaverLoadTimeWeaver

要指定特定的 Java 配置,请实现接口并重写该方法。 以下示例指定一个:LoadTimeWeaverLoadTimeWeavingConfigurergetLoadTimeWeaver()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 的配置,则可以指定完全限定的类名 作为元素上属性的值。同样,以下示例指定:weaver-class<context:load-time-weaver/>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>

由配置定义和注册的可以稍后 使用众所周知的名称从 Spring 容器中检索。 请记住,它仅作为 Spring 的 LTW 机制存在 基础结构来添加一个或多个 .执行 LTW 的实际值是 (从 package) 类。有关更多详细信息,请参阅该类的类级 javadoc,因为 织造实际影响超出了本文档的范围。LoadTimeWeaverloadTimeWeaverLoadTimeWeaverClassFileTransformersClassFileTransformerClassPreProcessorAgentAdapterorg.aspectj.weaver.loadtimeClassPreProcessorAgentAdapter

还有最后一个配置属性需要讨论:属性(或者如果使用 XML)。此属性控制 LTW 是否 是否启用。它接受三个可能的值之一,默认值为属性不存在时的值。下表总结了这三个 可能的值:aspectjWeavingaspectj-weavingautodetect

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

ENABLED

on

AspectJ 织造已开启,并根据需要在加载时织造方面。

DISABLED

off

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

AUTODETECT

autodetect

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

特定于环境的配置

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

Tomcat、JBoss、WildFly

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

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

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

通用 Java 应用程序

在不受 具体实现,JVM 代理是通用解决方案。 对于这种情况,Spring 提供了需要 特定于 Spring(但非常通用)的 JVM 代理,自动检测 通过通用和设置。LoadTimeWeaverInstrumentationLoadTimeWeaverspring-instrument.jar@EnableLoadTimeWeaving<context:load-time-weaver/>

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

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

请注意,这需要修改 JVM 启动脚本,这可能会阻止您 从在应用程序服务器环境中使用它(取决于您的服务器和 操作策略)。也就是说,对于每个 JVM 一个应用程序的部署,例如独立部署 Spring Boot 应用程序,在任何情况下,您通常都可以控制整个 JVM 设置。

此处提供的示例使用 XML 配置。您还可以配置和 将 @AspectJ 与 Java 配置一起使用。具体来说,您可以使用注释作为替代(有关详细信息,请参见下文)。@EnableLoadTimeWeaving<context:load-time-weaver/>
建议只编织特定的类(通常是 应用程序包,如上面的示例所示) 以避免 AspectJ 转储文件和警告等副作用。 从效率的角度来看,这也是一种最佳实践。aop.xml
此示例中使用的可能是基本的,但它非常有用。这是一个 开发人员在开发过程中可以使用的开发时方面的一个很好的例子 然后轻松地从正在部署的应用程序的构建中排除 进入UAT或生产。ProfilingAspect
建议只编织特定的类(通常是 应用程序包,如上面的示例所示) 以避免 AspectJ 转储文件和警告等副作用。 从效率的角度来看,这也是一种最佳实践。aop.xml
如果您不熟悉运行时类文件转换的概念,请参阅 在继续之前,包的 javadoc API 文档。 虽然该文档并不全面,但至少您可以看到关键界面 和类(供您阅读本节时参考)。java.lang.instrument
表 1.DefaultContextLoadTimeWeaver LoadTimeWeavers
运行时环境 LoadTimeWeaver实现

在 Apache Tomcat 中运行

TomcatLoadTimeWeaver

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

GlassFishLoadTimeWeaver

在 Red Hat 的 JBoss AS 或 WildFly 中运行

JBossLoadTimeWeaver

JVM 从 Spring 开始InstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar)

InstrumentationLoadTimeWeaver

回退,期望基础 ClassLoader 遵循常见约定 (即和可选的方法)addTransformergetThrowawayClassLoader

ReflectiveLoadTimeWeaver

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

ENABLED

on

AspectJ 织造已开启,并根据需要在加载时织造方面。

DISABLED

off

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

AUTODETECT

autodetect

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