此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.4! |
将 AspectJ 与 Spring 应用程序一起使用
到目前为止,我们在本章中介绍的所有内容都是纯粹的 Spring AOP。在本节中, 我们看看如何使用 AspectJ 编译器或 weaver 来代替 OR IN 如果您的需求超出了 Spring AOP 提供的功能,则可添加到 Spring AOP 中 独自。
Spring 附带了一个小型的 AspectJ 方面库,该库可在
distribution 为spring-aspects.jar
.您需要按顺序将其添加到 Classpath 中
以使用其中的 aspects。使用 AspectJ 对 Spring 和 Spring 的其他 Spring 方面进行依赖注入 Domain Objects 和 AspectJ 讨论
此库的内容以及如何使用它。使用 Spring IoC 配置 AspectJ 方面 讨论了如何
dependency 注入使用 AspectJ 编译器编织的 AspectJ 切面。最后,Spring 框架中使用 AspectJ 的加载时编织 提供了对 Spring 应用程序的加载时编织的介绍
使用 AspectJ。
使用 AspectJ 与 Spring 进行依赖注入域对象
Spring 容器实例化并配置应用程序中定义的 bean
上下文。也可以要求 bean factory 配置预先存在的
object,给定包含要应用的配置的 bean 定义的名称。spring-aspects.jar
包含一个 annotation-driven 切面,该 aspect 利用此
允许对任何对象进行依赖关系注入的能力。该支持旨在
用于在任何容器的控制之外创建的对象。域对象
通常属于这一类,因为它们通常是使用new
运算符,或者通过 ORM 工具作为数据库查询的结果。
这@Configurable
annotation 将类标记为符合 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 {
// ...
}
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 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>
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
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 now looks for a bean definition named account
and uses that as the
definition to configure new Account
instances.
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).
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.
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()).
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
-
Kotlin
@Configurable(preConstruction = true)
@Configurable(preConstruction = true)
You can find more information about the language semantics of the various pointcut
types in AspectJ
in this appendix of the
AspectJ Programming Guide.
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). If you
use Java-based configuration, you can add @EnableSpringConfigured
to any
@Configuration
class, as follows:
-
Java
-
Kotlin
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
@Configuration
@EnableSpringConfigured
class AppConfig {
}
If you prefer XML based configuration, the Spring
context
namespace
defines a convenient context:spring-configured
element, which you can use as follows:
<context:spring-configured/>
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>
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
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
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.
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).
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
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.
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.
A @Transactional
annotation on a class specifies the default transaction semantics for
the execution of any public operation in the class.
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.
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.
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
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.
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
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.
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>
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
Load-time weaving (LTW) refers to the process of weaving AspectJ aspects into an
application’s class files as they are being loaded into the Java virtual machine (JVM).
The focus of this section is on configuring and using LTW in the specific context of the
Spring Framework. This section is not a general introduction to LTW. For full details on
the specifics of LTW and configuring LTW with only AspectJ (with Spring not being
involved at all), see the
LTW section of the AspectJ
Development Environment Guide.
The value that the Spring Framework brings to AspectJ LTW is in enabling much
finer-grained control over the weaving process. 'Vanilla' AspectJ LTW is effected by using
a Java (5+) agent, which is switched on by specifying a VM argument when starting up a
JVM. It is, thus, a JVM-wide setting, which may be fine in some situations but is often a
little too coarse. Spring-enabled LTW lets you switch on LTW on a
per-ClassLoader
basis, which is more fine-grained and which can make more
sense in a 'single-JVM-multiple-application' environment (such as is found in a typical
application server environment).
Further, in certain environments, this support enables
load-time weaving without making any modifications to the application server’s launch
script that is needed to add -javaagent:path/to/aspectjweaver.jar
or (as we describe
later in this section) -javaagent:path/to/spring-instrument.jar
. Developers configure
the application context to enable load-time weaving instead of relying on administrators
who typically are in charge of the deployment configuration, such as the launch script.
Now that the sales pitch is over, let us first walk through a quick example of AspectJ
LTW that uses Spring, followed by detailed specifics about elements introduced in the
example. For a complete example, see the
Petclinic sample application based on Spring Framework.
A First Example
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.
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).
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
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() {
}
}
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>
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.
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>
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
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()
}
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
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.
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
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
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()
}
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.
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.
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
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
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>
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.
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)
At minimum, you need the following libraries to use the Spring Framework’s support
for AspectJ LTW:
-
spring-aop.jar
-
aspectjweaver.jar
If you use the Spring-provided agent to enable instrumentation
, you also need:
-
spring-instrument.jar
Spring Configuration
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.
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).
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
.)
To enable the Spring Framework’s LTW support, you need to configure a LoadTimeWeaver
,
which typically is done by using the @EnableLoadTimeWeaving
annotation, as follows:
-
Java
-
Kotlin
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}
Alternatively, if you prefer XML-based configuration, use the
<context:load-time-weaver/>
element. Note that the element is defined in the
context
namespace. The following example shows how to use <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>
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
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
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.
To specify a specific LoadTimeWeaver
with Java configuration, implement the
LoadTimeWeavingConfigurer
interface and override the getLoadTimeWeaver()
method.
The following example specifies a 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()
}
}
If you use XML-based configuration, you can specify the fully qualified class name
as the value of the weaver-class
attribute on the <context:load-time-weaver/>
element. Again, the following example specifies a 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>
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.
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
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 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
.
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
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.
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
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.