在Spring应用中使用AspectJ
本章前面介绍的内容都是纯 Spring AOP。在本节中,我们将探讨如何在需要超出 Spring AOP 单独提供的功能的情况下,使用 AspectJ 编译器或织入器来代替或补充 Spring AOP。
Spring 附带一个小型 AspectJ 方面库,可在您的发行版中作为spring-aspects.jar
独立使用。您需要将其添加到类路径中才能使用其中的方面。使用 AspectJ 通过 Spring 依赖注入领域对象 和 AspectJ 的其他 Spring 方面讨论了该库的内容以及如何使用它。使用 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 使用与完全限定类型名称 (在本例中为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 框架中使用 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
支持的目标之一是能够独立地对域对象进行单元测试,而无需处理与硬编码查找相关的困难。如果 AspectJ 没有编织@Configurable
类型,则该注解在单元测试期间不起作用。您可以设置被测试对象中的模拟或存根属性引用,然后照常进行。如果 AspectJ 已编织@Configurable
类型,您仍然可以像往常一样在容器外部进行单元测试,但是每次构建@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
方面。有关更多信息,请参阅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 方面
当您将 AspectJ 方面与 Spring 应用程序一起使用时,自然会希望并且期望能够使用 Spring 配置此类方面。AspectJ 运行时本身负责方面创建,并且通过 Spring 配置 AspectJ 创建的方面的方法取决于方面使用的 AspectJ 实例化模型(per-xxx
子句)。
大多数 AspectJ 方面是单例方面。这些方面的配置很容易。您可以创建一个引用方面类型作为普通 bean 的 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 运行时创建 bean 实例后,来做到这一点。
如果您有一些要使用 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) 指的是将 AspectJ 方面编织到应用程序的类文件中的过程,因为它们正在加载到 Java 虚拟机 (JVM) 中。本节的重点是在 Spring Framework 的特定上下文中配置和使用 LTW。本节不是对 LTW 的一般介绍。有关 LTW 的详细信息以及仅使用 AspectJ 配置 LTW(Spring 完全不参与)的详细信息,请参阅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
编织到我们的类中。此文件约定,即在名为META-INF/aop.xml
的 Java 类路径上存在一个文件(或多个文件),是标准的 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
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
类。
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 方面。此外,已编译的方面类需要在类路径上可用。
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>
建议仅编织特定类(通常是应用程序包中的类,如上面的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
:
-
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 部署) |
|
|
|
使用 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
稍后可以使用众所周知的名称loadTimeWeaver
从 Spring 容器中检索。请记住,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
,它能够进行本地检测。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 设置。