在 Spring 应用中使用 AspectJ
到目前为止,本章所涵盖的一切都是纯粹的 Spring AOP。在本节中,如果你的需求超出了 Spring AOP 独自提供的功能,我们将探讨如何使用 AspectJ 编译器或织入器来替代或补充 Spring AOP。
Spring 附带了一个小的 AspectJ 切面库,可在你的发行版中作为 spring-aspects.jar 独立使用。你需要将其添加到你的 classpath 中才能使用其中的切面。使用 AspectJ 为领域对象注入依赖与 Spring 和 用于 AspectJ 的其他 Spring 切面 讨论了该库的内容以及如何使用它。使用 Spring IoC 配置 AspectJ 切面 讨论了如何依赖注入使用 AspectJ 编译器织入的 AspectJ 切面。最后,Spring Framework 中使用 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 通过使用与完全限定类型名 (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
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())。
|
上一段中的一个关键词是“本质上”。对于大多数情况,“从新对象初始化返回后”的确切语义是可以的。在这种情况下,“初始化后”表示在对象构造完成后注入依赖项。这意味着依赖项在类的构造函数体中不可用。如果你希望在构造函数体运行之前注入依赖项,从而使其在构造函数体中可用,则需要在
你可以在 AspectJ 此附录 的 AspectJ 编程指南 中找到有关各种切入点类型语言语义的更多信息。 |
要使其正常工作,带注解的类型必须通过 AspectJ 织入器进行织入。你可以使用构建时 Ant 或 Maven 任务来完成此操作(例如,请参阅 AspectJ 开发环境指南),或者进行加载时织入(请参阅 Spring Framework 中使用 AspectJ 进行加载时织入)。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,它在被 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 放在 classpath 上。
考虑一个典型的 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 仅添加到容器范围的 classpath 中(因此由共享的父 ClassLoader 加载),所有 Web 应用程序都将共享相同的切面实例(这可能不是你想要的)。
用于 AspectJ 的其他 Spring 切面
除了 @Configurable 切面,spring-aspects.jar 还包含一个 AspectJ 切面,你可以用它来驱动 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 还包含你可以扩展的 abstract 切面,以提供你自己的切入点定义。有关更多信息,请参阅 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);
}
使用 Spring IoC 配置 AspectJ 切面
当你在 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 | 请注意 factory-method="aspectOf" 属性。 |
非单例切面更难配置。但是,可以通过创建原型 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 运行时并未涉及。 |
Spring Framework 中使用 AspectJ 进行加载时织入
加载时织入(LTW)是指在应用程序的类文件加载到 Java 虚拟机 (JVM) 中时,将 AspectJ 切面织入到这些类文件中的过程。本节的重点是在 Spring Framework 的特定上下文中配置和使用 LTW。本节不是 LTW 的一般介绍。有关 LTW 的具体细节以及仅使用 AspectJ(完全不涉及 Spring)配置 LTW 的完整详细信息,请参阅 AspectJ 开发环境指南的 LTW 部分。
Spring Framework 为 AspectJ LTW 带来的价值在于能够对织入过程进行更细粒度的控制。'普通' AspectJ LTW 是通过使用 Java (5+) 代理实现的,该代理通过在启动 JVM 时指定 VM 参数来打开。因此,它是一个 JVM 范围的设置,这在某些情况下可能没问题,但通常有点太粗糙了。Spring 启用的 LTW 允许你以每个 ClassLoader 为基础打开 LTW,这更细粒度,并且在“单 JVM-多应用程序”环境(例如典型的应用程序服务器环境)中可能更有意义。
此外,在某些环境中,此支持允许加载时织入,而无需修改应用程序服务器的启动脚本来添加 -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/> 的替代方案(有关详细信息,请参阅下文)。 |
以下示例显示了性能分析切面,它并不花哨。它是一个基于时间的性能分析器,使用 @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 classpath 上存在一个(或多个)名为 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 dump 文件和警告之类的副作用。从效率的角度来看,这也是一种最佳实践。 |
现在我们可以转到配置的 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
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,这是事实。但是,对于此示例,我们使用(Spring 提供的)Java 代理来打开 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 程序的轻微修改会产生相同的结果:
-
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 支持的基础知识已在前面的例子中全部介绍,本节的其余部分将详细解释每个配置和用法的“为什么”。
本示例中使用的 ProfilingAspect 可能很基础,但它非常有用。它是一个很好的开发时切面示例,开发人员可以在开发过程中使用,然后轻松地从部署到 UAT 或生产的应用程序构建中排除。 |
切面
你在 LTW 中使用的切面必须是 AspectJ 切面。你可以用 AspectJ 语言本身编写它们,也可以用 @AspectJ 风格编写你的切面。你的切面既是有效的 AspectJ 切面,也是有效的 Spring AOP 切面。此外,编译后的切面类需要在 classpath 上可用。
META-INF/aop.xml
AspectJ LTW 基础设施通过使用 Java classpath 上(直接或更常见地在 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>
建议仅织入特定的类(通常是应用程序包中的类,如上面 aop.xml 示例所示),以避免诸如 AspectJ dump 文件和警告之类的副作用。从效率的角度来看,这也是一种最佳实践。 |
此文件的结构和内容在 AspectJ 参考文档 的 LTW 部分中详细说明。因为 aop.xml 文件是 100% AspectJ,所以我们在此处不再赘述。
所需库 (JARs)
至少,你需要以下库才能使用 Spring Framework 对 AspectJ LTW 的支持:
-
spring-aop.jar -
aspectjweaver.jar
如果你使用 Spring 提供的代理来启用 instrumentation,你还需要:
-
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:
-
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,例如 LoadTimeWeaver 和 AspectJWeavingEnabler。默认的 LoadTimeWeaver 是 DefaultContextLoadTimeWeaver 类,它会尝试装饰一个自动检测到的 LoadTimeWeaver。具体“自动检测”到的 LoadTimeWeaver 类型取决于你的运行时环境。下表总结了各种 LoadTimeWeaver 实现:
| 运行时环境 | LoadTimeWeaver 实现 |
|---|---|
在 Apache Tomcat 中运行 |
|
在 GlassFish 中运行(限于 EAR 部署) |
|
|
|
JVM 随 Spring |
|
回退,期望底层 ClassLoader 遵循通用约定(即 |
|
请注意,该表仅列出了使用 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 以后可以从 Spring 容器中使用众所周知的名称 loadTimeWeaver 检索。请记住,LoadTimeWeaver 仅作为 Spring 的 LTW 基础设施添加一个或多个 ClassFileTransformers 的机制而存在。实际执行 LTW 的 ClassFileTransformer 是 ClassPreProcessorAgentAdapter(来自 org.aspectj.weaver.loadtime 包)类。有关更多详细信息,请参阅 ClassPreProcessorAgentAdapter 类的类级别 javadoc,因为织入实际如何实现的细节超出了本文档的范围。
配置中还有一个属性需要讨论:aspectjWeaving 属性(如果使用 XML,则是 aspectj-weaving)。此属性控制 LTW 是否启用。它接受三个可能值之一,如果属性不存在,则默认值为 autodetect。下表总结了三个可能的值:
| 注解值 | XML 值 | 解释 |
|---|---|---|
|
|
AspectJ 织入已打开,切面在加载时根据需要进行织入。 |
|
|
LTW 已关闭。没有切面在加载时被织入。 |
|
|
如果 Spring LTW 基础设施能够找到至少一个 |
特定环境配置
最后一部分包含在应用程序服务器和 Web 容器等环境中使用 Spring 的 LTW 支持时所需的任何附加设置和配置。
Tomcat, JBoss, WildFly
Tomcat 和 JBoss/WildFly 提供了一个通用的应用程序 ClassLoader,能够进行本地 instrumentation。Spring 的原生 LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 织入。你只需启用加载时织入,如前面所述。具体来说,你无需修改 JVM 启动脚本来添加 -javaagent:path/to/spring-instrument.jar。
请注意,在 JBoss 上,你可能需要禁用应用程序服务器扫描,以防止它在应用程序实际启动之前加载类。一个快速的解决方法是将名为 WEB-INF/jboss-scanning.xml 的文件添加到你的 artifact 中,内容如下:
<scanning xmlns="urn:jboss:scanning:1.0"/>
通用 Java 应用程序
当在不受特定 LoadTimeWeaver 实现支持的环境中需要类 instrumentation 时,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 设置。