Using AspectJ with Spring Applications

到目前为止,我们在本章中涵盖的所有内容都是纯粹的 Spring AOP。在本部分中,我们将研究如果您需要超出 Spring AOP 单独提供的功能,可以使用 AspectJ 编译器或织入器代替或补充 Spring AOP。

Everything we have covered so far in this chapter is pure Spring AOP. In this section, we look at how you can use the AspectJ compiler or weaver instead of or in addition to Spring AOP if your needs go beyond the facilities offered by Spring AOP alone.

Spring 随一小型的 AspectJ 方面库一起提供,您可在发行版中以独立方式获取该库,作为 spring-aspects.jar。您需要将此库添加到类路径中,以便使用其中的方面。Using AspectJ to Dependency Inject Domain Objects with SpringOther Spring aspects for AspectJ 讨论了此库的内容以及如何使用该库。Configuring AspectJ Aspects by Using Spring IoC 讨论如何使用 AspectJ 编译器编织的依赖注入 AspectJ 方面。最后, Load-time Weaving with AspectJ in the Spring Framework 提供了有关使用 AspectJ 的 Spring 应用程序负载时编织的简介。

Spring ships with a small AspectJ aspect library, which is available stand-alone in your distribution as spring-aspects.jar. You need to add this to your classpath in order to use the aspects in it. Using AspectJ to Dependency Inject Domain Objects with Spring and Other Spring aspects for AspectJ discuss the content of this library and how you can use it. Configuring AspectJ Aspects by Using Spring IoC discusses how to dependency inject AspectJ aspects that are woven using the AspectJ compiler. Finally, Load-time Weaving with AspectJ in the Spring Framework provides an introduction to load-time weaving for Spring applications that use AspectJ.

Using AspectJ to Dependency Inject Domain Objects with Spring

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

The Spring container instantiates and configures beans defined in your application context. It is also possible to ask a bean factory to configure a pre-existing object, given the name of a bean definition that contains the configuration to be applied. spring-aspects.jar contains an annotation-driven aspect that exploits this capability to allow dependency injection of any object. The support is intended to be used for objects created outside of the control of any container. Domain objects often fall into this category because they are often created programmatically with the new operator or by an ORM tool as a result of a database query.

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

The @Configurable annotation marks a class as being eligible for Spring-driven configuration. In the simplest case, you can use purely it as a marker annotation, as the following example shows:

  • Java

  • Kotlin

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

@Configurable
public class Account {
	// ...
}
import org.springframework.beans.factory.annotation.Configurable

@Configurable
class Account {
	// ...
}

以这种方式用作标记接口时,Spring 使用与类型全限定名称(在本例中为com.xyz.domain.Account)同名的 Bean 定义(通常是原型作用域的)来配置注解类型(Account)的新实例。由于通过 XML 定义的 Bean 的默认名称是其类型的全限定名称,因此声明原型定义的便捷方法是省略id属性,如下例所示:

When used as a marker interface in this way, Spring configures new instances of the annotated type (Account, in this case) by using a bean definition (typically prototype-scoped) with the same name as the fully-qualified type name (com.xyz.domain.Account). Since the default name for a bean defined via XML is the fully-qualified name of its type, a convenient way to declare the prototype definition is to omit the id attribute, as the following example shows:

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

如果您想明确指定要使用的原型 Bean 定义的名称,可以在注解中直接这样做,如下例所示:

If you want to explicitly specify the name of the prototype bean definition to use, you can do so directly in the annotation, as the following example shows:

  • Java

  • Kotlin

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

@Configurable("account")
public class Account {
	// ...
}
import org.springframework.beans.factory.annotation.Configurable

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

现在 Spring 查找一个名为account的 Bean 定义,并将其用作配置新Account实例的定义。

Spring now looks for a bean definition named account and uses that as the definition to configure new Account instances.

您还可以使用自动装配来避免必须指定任何专用 Bean 定义。要让 Spring 应用自动装配,请使用 @Configurable 注解的 autowire 属性。您可以指定 @Configurable(autowire=Autowire.BY_TYPE)@Configurable(autowire=Autowire.BY_NAME) 分别通过类型或名称进行自动装配。作为一种选择,最好通过 @Autowired@Inject 在字段或方法级别为 @Configurable Beans 指定显式注解驱动的依赖项注入(有关更多详细信息,请参见 Annotation-based Container Configuration)。

You can also use autowiring to avoid having to specify a dedicated bean definition at all. To have Spring apply autowiring, use the autowire property of the @Configurable annotation. You can specify either @Configurable(autowire=Autowire.BY_TYPE) or @Configurable(autowire=Autowire.BY_NAME) for autowiring by type or by name, respectively. As an alternative, it is preferable to specify explicit, annotation-driven dependency injection for your @Configurable beans through @Autowired or @Inject at the field or method level (see Annotation-based Container Configuration for further details).

最后,您可以使用dependencyCheck属性(例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true))为新创建和配置的对象中的对象引用启用 Spring 依赖项检查。如果将此属性设置为true,则 Spring 在配置后验证所有属性(不是基本类型或集合)是否已设置。

Finally, you can enable Spring dependency checking for the object references in the newly created and configured object by using the dependencyCheck attribute (for example, @Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)). If this attribute is set to true, Spring validates after configuration that all properties (which are not primitives or collections) have been set.

请注意,单独使用注解不起作用。spring-aspects.jar 中的 AnnotationBeanConfigurerAspect 是对注解存在形式执行操作的。从本质上讲,此方面表示,“从使用 @Configurable 进行注解的新类型对象的初始化返回后,使用 Spring 根据注解的属性配置新建对象”。在此上下文中,“初始化”是指新实例化的对象(例如,使用 new 运算符实例化的对象),以及正在反序列化的 Serializable 对象(例如,通过 readResolve())。

Note that using the annotation on its own does nothing. It is the AnnotationBeanConfigurerAspect in spring-aspects.jar that acts on the presence of the annotation. In essence, the aspect says, "after returning from the initialization of a new object of a type annotated with @Configurable, configure the newly created object using Spring in accordance with the properties of the annotation". In this context, "initialization" refers to newly instantiated objects (for example, objects instantiated with the new operator) as well as to Serializable objects that are undergoing deserialization (for example, through readResolve()).

上面一段中的关键短语之一是“从本质上讲”。对于大多数情况,确切的语义“在对新对象的初始化返回后”都很好。在此上下文中,“初始化后”意味着在对象构建后注入依赖项。这意味着构造函数主体内无法使用依赖项。如果您希望在构造函数主体运行之前注入依赖项,从而可以在构造函数主体中使用它们,则需要在@Configurable声明中定义此内容,如下所示:

One of the key phrases in the above paragraph is "in essence". For most cases, the exact semantics of "after returning from the initialization of a new object" are fine. In this context, "after initialization" means that the dependencies are injected after the object has been constructed. This means that the dependencies are not available for use in the constructor bodies of the class. If you want the dependencies to be injected before the constructor bodies run and thus be available for use in the body of the constructors, you need to define this on the @Configurable declaration, as follows:

Java
@Configurable(preConstruction = true)
Kotlin
@Configurable(preConstruction = true)

您可以在 此附录 中的https://www.eclipse.org/aspectj/doc/released/progguide/index.html[AspectJ 编程指南] 中找到有关各种 AspectJ 切入点类型语言语义的更多信息。

You can find more information about the language semantics of the various pointcut types in AspectJ in this appendix of the AspectJ Programming Guide.

为此,注释类型必须使用 AspectJ weaver 编织。您可以使用构建时 Ant 或 Maven 任务执行此操作(例如,请参阅 AspectJ 开发环境指南),或使用负载时编织(请参阅 Load-time Weaving with AspectJ in the Spring Framework)。AnnotationBeanConfigurerAspect 本身需要由 Spring 配置(为了获得要用来配置新对象的 Bean 工厂的引用)。您可以按如下方式定义相关配置:

For this to work, the annotated types must be woven with the AspectJ weaver. You can either use a build-time Ant or Maven task to do this (see, for example, the AspectJ Development Environment Guide) or load-time weaving (see Load-time Weaving with AspectJ in the Spring Framework). The AnnotationBeanConfigurerAspect itself needs to be configured by Spring (in order to obtain a reference to the bean factory that is to be used to configure new objects). You can define the related configuration as follows:

  • 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,该 bean 在被 Spring 初始化时创建域对象。在这种情况下,您可以使用depends-on bean 属性手动指定 bean 依赖于配置方面。以下示例说明如何使用depends-on属性:

Instances of @Configurable objects created before the aspect has been configured result in a message being issued to the debug log and no configuration of the object taking place. An example might be a bean in the Spring configuration that creates domain objects when it is initialized by Spring. In this case, you can use the depends-on bean attribute to manually specify that the bean depends on the configuration aspect. The following example shows how to use the depends-on attribute:

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

	<!-- ... -->

</bean>

除非你真的想在运行时依赖它的语义,否则不要通过 bean 配置器方面激活 @Configurable 处理。特别是,请确保您未在作为常规 Spring bean 使用容器注册的 bean 类上使用 @Configurable。执行此操作会导致双重初始化,一次通过容器,一次通过方面。

Do not activate @Configurable processing through the bean configurer aspect unless you really mean to rely on its semantics at runtime. In particular, make sure that you do not use @Configurable on bean classes that are registered as regular Spring beans with the container. Doing so results in double initialization, once through the container and once through the aspect.

Unit Testing @Configurable Objects

@Configurable 中给出的支持的一个目标在于避免进行硬编码的查找,从而支持对领域目标进行独立的单元测试。如果 AspectJ 没有编织 @Configurable 类型,该对象无法在单元测试中产生任何影响。你可以在测试目标中设置模拟或存根的属性引用,并且像往常一样进行操作。如果 @Configurable 类型已经由 AspectJ 编织,你仍然可以在容器之外像往常一样执行单元测试,但每次构建一个 @Configurable 目标时你都将看到一条警告信息,指明该目标尚未由 Spring 配置。

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.

Working with Multiple Application Contexts

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

The AnnotationBeanConfigurerAspect that is used to implement the @Configurable support is an AspectJ singleton aspect. The scope of a singleton aspect is the same as the scope of static members: There is one aspect instance per ClassLoader that defines the type. This means that, if you define multiple application contexts within the same ClassLoader hierarchy, you need to consider where to define the @EnableSpringConfigured bean and where to place spring-aspects.jar on the classpath.

考虑一个典型的 Spring Web 应用程序配置,其中有一个共享的父 ApplicationContext,它定义了公共业务服务、支持这些服务所需的一切,以及一个针对每个 Servlet(其中包含对该 servlet 特有的定义)的子 ApplicationContext。所有这些上下文在相同的 ClassLoader 层次结构中并存,因此 AnnotationBeanConfigurerAspect 只能引用其中的一个。在这种情况下,我们建议在共享(父级)应用程序上下文中定义 @EnableSpringConfigured Bean。这定义了你可能想要注入到领域对象中的服务。这样会产生一个后果,即你使用 @Configurable 机制无法通过引用在子(特定 servlet)上下文中定义的 Bean 来配置领域对象(这可能并不是你要做的)。

Consider a typical Spring web application configuration that has a shared parent application context that defines common business services, everything needed to support those services, and one child application context for each servlet (which contains definitions particular to that servlet). All of these contexts co-exist within the same ClassLoader hierarchy, and so the AnnotationBeanConfigurerAspect can hold a reference to only one of them. In this case, we recommend defining the @EnableSpringConfigured bean in the shared (parent) application context. This defines the services that you are likely to want to inject into domain objects. A consequence is that you cannot configure domain objects with references to beans defined in the child (servlet-specific) contexts by using the @Configurable mechanism (which is probably not something you want to do anyway).

在同一个容器中部署多个 Web 应用程序时,请确保每个 Web 应用程序使用自己的 ClassLoader 来加载 spring-aspects.jar 中的类型(例如,将 spring-aspects.jar 放置在 WEB-INF/lib 中)。如果仅将 spring-aspects.jar 添加到全容器类路径中(因此由共享的父 ClassLoader 加载),所有 Web 应用程序将共享相同的面向实例(这可能不是您想要的)。

When deploying multiple web applications within the same container, ensure that each web application loads the types in spring-aspects.jar by using its own ClassLoader (for example, by placing spring-aspects.jar in WEB-INF/lib). If spring-aspects.jar is added only to the container-wide classpath (and hence loaded by the shared parent ClassLoader), all web applications share the same aspect instance (which is probably not what you want).

Other Spring aspects for AspectJ

@Configurable 切面外,spring-aspects.jar 还包含一个 AspectJaspect,你可以使用该切面来驱动 Spring 事务管理,类型和方法上带有 @Transactional 注解。这主要适用于希望在 Spring 容器外部使用 Spring Framework 事务支持的用户。

In addition to the @Configurable aspect, spring-aspects.jar contains an AspectJ aspect that you can use to drive Spring’s transaction management for types and methods annotated with the @Transactional annotation. This is primarily intended for users who want to use the Spring Framework’s transaction support outside of the Spring container.

解释 @Transactional 注释的方面是 AnnotationTransactionAspect。使用此方面时,必须对实现类(或该类中的方法或二者)添加注释, وليس 实现了类的接口(如果存在)。AspectJ 遵循 Java 的规则,即接口上的注释不会被继承。

The aspect that interprets @Transactional annotations is the AnnotationTransactionAspect. When you use this aspect, you must annotate the implementation class (or methods within that class or both), not the interface (if any) that the class implements. AspectJ follows Java’s rule that annotations on interfaces are not inherited.

类上的 @Transactional 注解指定任何公开操作的执行的默认事务语义。

A @Transactional annotation on a class specifies the default transaction semantics for the execution of any public operation in the class.

类中的某个方法上的 @Transactional 注释会覆盖类注释(如果存在)给出的默认事务语义。任何可见性的方法都可以进行注释,包括私有方法。直接注释非公共方法是唯一的方法来为这些方法的执行获取事务分界。

A @Transactional annotation on a method within the class overrides the default transaction semantics given by the class annotation (if present). Methods of any visibility may be annotated, including private methods. Annotating non-public methods directly is the only way to get transaction demarcation for the execution of such methods.

从 Spring Framework 4.2 开始,spring-aspects 提供了一个类似的方面,它为标准 jakarta.transaction.Transactional 注释提供了完全相同的功能。查看 JtaAnnotationTransactionAspect 了解更多详情。

Since Spring Framework 4.2, spring-aspects provides a similar aspect that offers the exact same features for the standard jakarta.transaction.Transactional annotation. Check JtaAnnotationTransactionAspect for more details.

对于希望使用 Spring 配置和事务管理支持但不想(或不能)使用注释的 AspectJ 程序员来说,“spring-aspects.jar”也包含你可以扩展以提供自己的切点定义的“抽象”方面。有关更多信息,请参阅“AbstractBeanConfigurerAspect”和“AbstractTransactionAspect”方面的来源。例如,以下摘录展示了如何编写一个方面来配置域模型中定义的所有对象实例,方法是使用与完全限定类名称匹配的原型 Bean 定义:

For AspectJ programmers who want to use the Spring configuration and transaction management support but do not want to (or cannot) use annotations, spring-aspects.jar also contains abstract aspects you can extend to provide your own pointcut definitions. See the sources for the AbstractBeanConfigurerAspect and AbstractTransactionAspect aspects for more information. As an example, the following excerpt shows how you could write an aspect to configure all instances of objects defined in the domain model by using prototype bean definitions that match the fully qualified class names:

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);
}

Configuring AspectJ Aspects by Using Spring IoC

在 Spring 应用程序中使用 AspectJ 方方面面时,自然会希望并且期望能够使用 Spring 配置这些方方面面。AspectJ 运行时本身负责创建方面,通过 Spring 配置 AspectJ 创建的方面的方法取决于方面使用的 AspectJ 实例化模型(per-xxx 子句)。

When you use AspectJ aspects with Spring applications, it is natural to both want and expect to be able to configure such aspects with Spring. The AspectJ runtime itself is responsible for aspect creation, and the means of configuring the AspectJ-created aspects through Spring depends on the AspectJ instantiation model (the per-xxx clause) used by the aspect.

大多数 AspectJ 方面是单例方面。这些方面的配置很简单。您可以创建一个引用方面类型的 Bean 定义,与普通方面一样,并包含 factory-method="aspectOf" Bean 属性。这可确保 Spring 通过询问 AspectJ 来获取方面实例,而不是尝试自己创建实例。以下示例展示了如何使用 factory-method="aspectOf" 属性:

The majority of AspectJ aspects are singleton aspects. Configuration of these aspects is easy. You can create a bean definition that references the aspect type as normal and include the factory-method="aspectOf" bean attribute. This ensures that Spring obtains the aspect instance by asking AspectJ for it rather than trying to create an instance itself. The following example shows how to use the factory-method="aspectOf" attribute:

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

	<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
1 Note the factory-method="aspectOf" attribute

非单例方面更难配置。然而,可以通过创建原型 bean 定义并且使用 spring-aspects.jar 中的 @Configurable 支持来配置方面实例,一旦它们被 AspectJ 运行时创建,即可完成。

Non-singleton aspects are harder to configure. However, it is possible to do so by creating prototype bean definitions and using the @Configurable support from spring-aspects.jar to configure the aspect instances once they have bean created by the AspectJ runtime.

如果您有一些希望使用 AspectJ 编织的 @AspectJ 方面(例如,为领域模型类型使用加载时间编织),还有一些其他希望与 Spring AOP 一起使用的 @AspectJ 方面,并且这些方面全部都在 Spring 中进行配置,您需要告诉 Spring AOP、@AspectJ 自动代理支持,应为自动代理使用配置中定义的哪一个 @AspectJ 方面的子集。您可以通过在 <aop:aspectj-autoproxy/> 声明中使用一个或多个 <include/> 元素来完成此操作。每个 <include/> 元素指定一个名称模式,并且仅将与至少一个模式匹配的名称 Bean 用于 Spring AOP 自动代理配置。以下示例展示如何使用 <include/> 元素:

If you have some @AspectJ aspects that you want to weave with AspectJ (for example, using load-time weaving for domain model types) and other @AspectJ aspects that you want to use with Spring AOP, and these aspects are all configured in Spring, you need to tell the Spring AOP @AspectJ auto-proxying support which exact subset of the @AspectJ aspects defined in the configuration should be used for auto-proxying. You can do this by using one or more <include/> elements inside the <aop:aspectj-autoproxy/> declaration. Each <include/> element specifies a name pattern, and only beans with names matched by at least one of the patterns are used for Spring AOP auto-proxy configuration. The following example shows how to use <include/> elements:

<aop:aspectj-autoproxy>
	<aop:include name="thisBean"/>
	<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>

不要被 <aop:aspectj-autoproxy/> 元素的名称误导。使用它会导致创建 Spring AOP 代理。此处使用了面向 AspectJ 风格的方面声明,但 AspectJ 运行时并未涉及进来。

Do not be misled by the name of the <aop:aspectj-autoproxy/> element. Using it results in the creation of Spring AOP proxies. The @AspectJ style of aspect declaration is being used here, but the AspectJ runtime is not involved.

Load-time Weaving with AspectJ in the Spring Framework

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

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.

Spring Framework 为 AspectJ LTW 带来的价值在于能够更细粒度地控制 Weaving 过程。“纯”AspectJ LTW 是通过使用 Java(5+)代理来实现的,该代理在启动 JVM 时通过指定 VM 参数来开启。因此,它是 JVM 级别的设置,在某些情况下可能很好,但在很多情况下都有些粗略。Spring 启用的 LTW 让你能够基于按 ClassLoader 级别开启 LTW,这更加细粒度,并且在“单 JVM-多应用程序”环境中(例如典型的应用程序服务器环境中)更有意义。

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).

此外,in certain environments,此支持启用了负载时编织,而无需对应用程序服务器启动脚本进行任何修改,该脚本需要添加 -javaagent:path/to/aspectjweaver.jar 或(如下文所述)-javaagent:path/to/spring-instrument.jar。开发人员配置应用程序上下文以启用负载时编织,而不是依靠通常负责部署配置(例如启动脚本)的管理员。

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.

现在,在产品宣传结束之后,首先让我们快速浏览一个使用 Spring 的 AspectJ LTW 的示例,然后详细了解示例中引入的元素。有关完整示例,请参见 Petclinic 样本应用程序

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.

A First Example

假设您是一位应用程序开发者, tasked 处置诊断系统中某些性能问题的原因。但不是拿出分析工具,我们将开启一个简单的分析环节,让我们快速获取一些性能指标。然后,我们马上就可以对那个特定区域应用一个精细的分析工具。

Assume that you are an application developer who has been tasked with diagnosing the cause of some performance problems in a system. Rather than break out a profiling tool, we are going to switch on a simple profiling aspect that lets us quickly get some performance metrics. We can then apply a finer-grained profiling tool to that specific area immediately afterwards.

此处提供的示例使用 XML 配置。你还可以用 Java configuration 来配置和使用 @AspectJ 。具体来说,你可以使用 @EnableLoadTimeWeaving 注解作为 <context:load-time-weaver/> 的替代品(有关详细信息,请参阅 below)。

The example presented here uses XML configuration. You can also configure and use @AspectJ with Java configuration. Specifically, you can use the @EnableLoadTimeWeaving annotation as an alternative to <context:load-time-weaver/> (see below for details).

以下示例展示了剖析方面,该方面很简单。它是一个基于时间的剖析器,它使用AspectJ 形式的方面声明:

The following example shows the profiling aspect, which is not fancy. It is a time-based profiler that uses the @AspectJ-style of aspect declaration:

  • Java

  • Kotlin

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(){}
}
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 weaver,我们希望将 ProfilingAspect 编织到我们的类中。此文件约定,即在称为 META-INF/aop.xml 的 Java classpath 中存在一个文件(或多个文件),是标准的 AspectJ。以下示例显示 aop.xml 文件:

We also need to create an META-INF/aop.xml file, to inform the AspectJ weaver that we want to weave our ProfilingAspect into our classes. This file convention, namely the presence of a file (or files) on the Java classpath called META-INF/aop.xml is standard AspectJ. The following example shows the aop.xml file:

<!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 转储文件和警告。从效率的角度来看,这也是最佳实践。

It is recommended to only weave specific classes (typically those in the application packages, as shown in the aop.xml example above) in order to avoid side effects such as AspectJ dump files and warnings. This is also a best practice from an efficiency perspective.

现在我们可以继续进行配置中特定于 Spring 的部分了。我们需要配置一个 LoadTimeWeaver(稍后解释)。此装载时织入程序是负责将一个或多个 META-INF/aop.xml 文件中的方面配置织入应用程序中的类的关键组件。好处是它不需要很多配置(你可以指定更多选项,但稍后会详细说明),如下例所示:

Now we can move on to the Spring-specific portion of the configuration. We need to configure a LoadTimeWeaver (explained later). This load-time weaver is the essential component responsible for weaving the aspect configuration in one or more META-INF/aop.xml files into the classes in your application. The good thing is that it does not require a lot of configuration (there are some more options that you can specify, but these are detailed later), as can be seen in the following example:

<?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 的作用:

Now that all the required artifacts (the aspect, the META-INF/aop.xml file, and the Spring configuration) are in place, we can create the following driver class with a main(..) method to demonstrate the LTW in action:

  • Java

  • Kotlin

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

我们还有一件事要做。本节的引言确实表示,可以基于每个ClassLoader 的基础通过 Spring 选择性地启动 LTW,这是正确的。但是,对于此示例,我们使用 Java 代理(随 Spring 提供)来启动 LTW。我们使用以下命令运行前面显示的Main 类:

We have one last thing to do. The introduction to this section did say that one could switch on LTW selectively on a per-ClassLoader basis with Spring, and this is true. However, for this example, we use a Java agent (supplied with Spring) to switch on LTW. We use the following command to run the Main class shown earlier:

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

-javaagent 是指定和启用 用于对在 JVM 上运行的程序进行仪器的 agent 的标志。Spring 框架附带这样一个 agent,即 InstrumentationSavingAgent,它包装在 @{26} 中,该包作为上述示例中 -javaagent 参数的值提供。

The -javaagent is a flag for specifying and enabling agents to instrument programs that run on the JVM. The Spring Framework ships with such an agent, the InstrumentationSavingAgent, which is packaged in the spring-instrument.jar that was supplied as the value of the -javaagent argument in the preceding example.

Main 程序执行的输出类似于下一个示例。(我在 calculateEntitlement() 实现中引入了一个 Thread.sleep(..) 语句,以便性能分析器实际上捕捉到除了 0 毫秒之外的内容(01234 毫秒不是 AOP 引入的开销)。以下清單顯示我們運行性能分析器時取得的輸出:

The output from the execution of the Main program looks something like the next example. (I have introduced a Thread.sleep(..) statement into the calculateEntitlement() implementation so that the profiler actually captures something other than 0 milliseconds (the 01234 milliseconds is not an overhead introduced by the AOP). The following listing shows the output we got when we ran our profiler:

Calculating entitlement

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

由于该 LTW 使用全功能的 AspectJ,我们不仅限于通知 Spring bean。以下 Main 程序的轻微变化产生相同结果:

Since this LTW is effected by using full-blown AspectJ, we are not limited only to advising Spring beans. The following slight variation on the Main program yields the same result:

  • Java

  • Kotlin

// 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();
	}
}
// 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 新实例。概要文件建议仍会编织进来。

Notice how, in the preceding program, we bootstrap the Spring container and then create a new instance of the StubEntitlementCalculationService totally outside the context of Spring. The profiling advice still gets woven in.

不可否认的是,这个例子很简单。但是,Spring 中的 LTW 支持基础知识已在前面的示例中全面介绍,本节其余部分将详细说明配置和使用的每一部分背后的“原因”。

Admittedly, the example is simplistic. However, the basics of the LTW support in Spring have all been introduced in the earlier example, and the rest of this section explains the "why" behind each bit of configuration and usage in detail.

本示例中使用的 ProfilingAspect 可能比较基础,但非常有用。它是开发人员在开发过程中可以使用的开发期方面的良好示例,然后可以轻松地将其从部署到 UAT 或生产中的应用程序版本中排除。

The ProfilingAspect used in this example may be basic, but it is quite useful. It is a nice example of a development-time aspect that developers can use during development and then easily exclude from builds of the application being deployed into UAT or production.

Aspects

您在 LTW 中使用的方面必须是 AspectJ 方面。您可以使用 AspectJ 语言编写它们,也可以使用 @AspectJ 样式编写它们。然后,您的方面同时是有效的 AspectJ 和 Spring AOP 方面。此外,编译过的方面类需要在类路径上可用。

The aspects that you use in LTW have to be AspectJ aspects. You can write them in either the AspectJ language itself, or you can write your aspects in the @AspectJ-style. Your aspects are then both valid AspectJ and Spring AOP aspects. Furthermore, the compiled aspect classes need to be available on the classpath.

META-INF/aop.xml

AspectJ LTW 基础设施使用一个或多个 META-INF/aop.xml 文件进行配置,这些文件位于 Java 类路径上(直接或通常在 jar 文件中)。例如:

The AspectJ LTW infrastructure is configured by using one or more META-INF/aop.xml files that are on the Java classpath (either directly or, more typically, in jar files). For example:

<!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 转储文件和警告。从效率的角度来看,这也是最佳实践。

It is recommended to only weave specific classes (typically those in the application packages, as shown in the aop.xml example above) in order to avoid side effects such as AspectJ dump files and warnings. This is also a best practice from an efficiency perspective.

此文件的结构和内容在 AspectJ 参考文档 的 LTW 部分中进行了详细说明。由于 aop.xml 文件是 100% AspectJ,因此我们在此不再对其进行进一步说明。

The structure and contents of this file is detailed in the LTW part of the AspectJ reference documentation. Because the aop.xml file is 100% AspectJ, we do not describe it further here.

Required libraries (JARS)

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

At minimum, you need the following libraries to use the Spring Framework’s support for AspectJ LTW:

  • spring-aop.jar

  • aspectjweaver.jar

如果您使用 Spring-provided agent to enable instrumentation,您还需要:

If you use the Spring-provided agent to enable instrumentation , you also need:

  • spring-instrument.jar

Spring Configuration

Spring 的 LTW 支持中的关键组件是 LoadTimeWeaver 接口(位于 org.springframework.instrument.classloading 包中),以及随 Spring 发行版一起提供的众多实现。LoadTimeWeaver 负责在运行时向 ClassLoader 添加一个或多个 java.lang.instrument.ClassFileTransformers,从而为各种有趣的应用程序打开了大门,其中之一就是方面的 LTW。

The key component in Spring’s LTW support is the LoadTimeWeaver interface (in the org.springframework.instrument.classloading package), and the numerous implementations of it that ship with the Spring distribution. A LoadTimeWeaver is responsible for adding one or more java.lang.instrument.ClassFileTransformers to a ClassLoader at runtime, which opens the door to all manner of interesting applications, one of which happens to be the LTW of aspects.

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

If you are unfamiliar with the idea of runtime class file transformation, see the javadoc API documentation for the java.lang.instrument package before continuing. While that documentation is not comprehensive, at least you can see the key interfaces and classes (for reference as you read through this section).

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

Configuring a LoadTimeWeaver for a particular ApplicationContext can be as easy as adding one line. (Note that you almost certainly need to use an ApplicationContext as your Spring container — typically, a BeanFactory is not enough because the LTW support uses BeanFactoryPostProcessors.)

要启用 Spring Framework 的 LTW 支持,您需要按如下方式配置 LoadTimeWeaver

To enable the Spring Framework’s LTW support, you need to configure a LoadTimeWeaver as follows:

  • 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(例如 LoadTimeWeaverAspectJWeavingEnabler)进行定义和注册。默认的 LoadTimeWeaverDefaultContextLoadTimeWeaver 类,它尝试装饰自动检测到的 LoadTimeWeaver。“自动检测”的确切类型 LoadTimeWeaver 取决于您的运行时环境。下表总结了各种 LoadTimeWeaver 实现:

The preceding configuration automatically defines and registers a number of LTW-specific infrastructure beans, such as a LoadTimeWeaver and an AspectJWeavingEnabler, for you. The default LoadTimeWeaver is the DefaultContextLoadTimeWeaver class, which attempts to decorate an automatically detected LoadTimeWeaver. The exact type of LoadTimeWeaver that is "automatically detected" is dependent upon your runtime environment. The following table summarizes various LoadTimeWeaver implementations:

Table 1. DefaultContextLoadTimeWeaver LoadTimeWeavers
Runtime Environment LoadTimeWeaver implementation

Running in Apache Tomcat

TomcatLoadTimeWeaver

Running in GlassFish (limited to EAR deployments)

GlassFishLoadTimeWeaver

Running in Red Hat’s JBoss AS or WildFly

JBossLoadTimeWeaver

JVM started with Spring InstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar)

InstrumentationLoadTimeWeaver

Fallback, expecting the underlying ClassLoader to follow common conventions (namely addTransformer and optionally a getThrowawayClassLoader method)

ReflectiveLoadTimeWeaver

请注意,当您使用 DefaultContextLoadTimeWeaver 时,该表只列出了自动检测到的 LoadTimeWeavers。您可以确切地指定要使用的 LoadTimeWeaver 实现。

Note that the table lists only the LoadTimeWeavers that are autodetected when you use the DefaultContextLoadTimeWeaver. You can specify exactly which LoadTimeWeaver implementation to use.

要配置特定的 LoadTimeWeaver,实现 LoadTimeWeavingConfigurer 接口并覆盖 getLoadTimeWeaver() 方法(或使用 XML 等效项)。以下示例指定了 ReflectiveLoadTimeWeaver

To configure a specific LoadTimeWeaver, implement the LoadTimeWeavingConfigurer interface and override the getLoadTimeWeaver() method (or use the XML equivalent). The following example specifies a 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 的实际 ClassFileTransformerClassPreProcessorAgentAdapter(来自 org.aspectj.weaver.loadtime 包)类。请参见 ClassPreProcessorAgentAdapter 类的类级 javadoc 以获取更多详细信息,因为实际如何进行编织的具体细节超出了本文档的范围。

The LoadTimeWeaver that is defined and registered by the configuration can be later retrieved from the Spring container by using the well known name, loadTimeWeaver. Remember that the LoadTimeWeaver exists only as a mechanism for Spring’s LTW infrastructure to add one or more ClassFileTransformers. The actual ClassFileTransformer that does the LTW is the ClassPreProcessorAgentAdapter (from the org.aspectj.weaver.loadtime package) class. See the class-level javadoc of the ClassPreProcessorAgentAdapter class for further details, because the specifics of how the weaving is actually effected is beyond the scope of this document.

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

There is one final attribute of the configuration left to discuss: the aspectjWeaving attribute (or aspectj-weaving if you use XML). This attribute controls whether LTW is enabled or not. It accepts one of three possible values, with the default value being autodetect if the attribute is not present. The following table summarizes the three possible values:

Table 2. AspectJ weaving attribute values
Annotation Value XML Value Explanation

ENABLED

on

AspectJ weaving is on, and aspects are woven at load-time as appropriate.

DISABLED

off

LTW is off. No aspect is woven at load-time.

AUTODETECT

autodetect

If the Spring LTW infrastructure can find at least one META-INF/aop.xml file, then AspectJ weaving is on. Otherwise, it is off. This is the default value.

Environment-specific Configuration

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

This last section contains any additional settings and configuration that you need when you use Spring’s LTW support in environments such as application servers and web containers.

Tomcat, JBoss, WildFly

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

Tomcat and JBoss/WildFly provide a general app ClassLoader that is capable of local instrumentation. Spring’s native LTW may leverage those ClassLoader implementations to provide AspectJ weaving. You can simply enable load-time weaving, as described earlier. Specifically, you do not need to modify the JVM launch script to add -javaagent:path/to/spring-instrument.jar.

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

Note that on JBoss, you may need to disable the app server scanning to prevent it from loading the classes before the application actually starts. A quick workaround is to add to your artifact a file named WEB-INF/jboss-scanning.xml with the following content:

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

Generic Java Applications

当在不受特定 LoadTimeWeaver 实现支持的环境中需要类检测时,JVM 代理是通用的解决方案。对于这样的情况,Spring 提供 InstrumentationLoadTimeWeaver,它需要 Spring 特定的(但非常通用的)JVM 代理 spring-instrument.jar,这些代理可通过常见的 @EnableLoadTimeWeaving<context:load-time-weaver/> 设置自动检测。

When class instrumentation is required in environments that are not supported by specific LoadTimeWeaver implementations, a JVM agent is the general solution. For such cases, Spring provides InstrumentationLoadTimeWeaver which requires a Spring-specific (but very general) JVM agent, spring-instrument.jar, autodetected by common @EnableLoadTimeWeaving and <context:load-time-weaver/> setups.

要使用它,您需要向 Spring 代理提供以下 JVM 选项来启动虚拟机:

To use it, you must start the virtual machine with the Spring agent by supplying the following JVM options:

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

请注意,这需要修改 JVM 启动脚本,这可能会妨碍你在应用程序服务器环境中使用此功能(具体视你的服务器和你的操作策略而定)。也就是说,对于诸如独立 Spring Boot 应用程序的一应用每 JVM 部署,你通常在任何情况下都控制整个 JVM 设置。

Note that this requires modification of the JVM launch script, which may prevent you from using this in application server environments (depending on your server and your operation policies). That said, for one-app-per-JVM deployments such as standalone Spring Boot applications, you typically control the entire JVM setup in any case.