Load-time weaving (LTW) refers to the process of binding AspectJ aspects to application class files while they are loaded into the Java virtual machine JVM). This section focuses on setting up and using the LTW mechanism in the specific context of the Spring Framework. This section is not a general introduction to boot-time binding. For full details on the specifics of the boot-time binding mechanism and setting it up using only AspectJ (no Spring involved at all) see Section on load-time binding in the AspectJ Development Environment Guide.
The value of the Spring Framework for
load-time binding from AspectJ is that it allows much more fine-grained control of the binding process. The
vanilla boot-time binding mechanism from AspectJ is done using a Java (5+) agent, which is enabled by specifying a
virtual machine argument when the JVM starts. So this is a JVM-wide setting that can be good in some situations, but
is often too crude. The Spring-enabled LTW mechanism allows it to be enabled on a per ClassLoader
basis, which is a more nuanced setting and may make much more sense in an environment with one JVM but many
applications (such as a typical application server environment) .
Furthermore, in certain environments, this
support allows binding at boot time without making the changes to the application server startup script required to
add -javaagent:path/to/aspectjweaver.jar
or (as described later in this section) -javaagent:path/to/spring-instrument.jar
.
Developers configure the application context to enable binding at boot time, rather than relying on administrators
who are typically responsible for deployment configuration, such as startup scripts.
Now that we're done praising, let's look at a quick LTW example from AspectJ using Spring, and then go into detail about the elements presented in the example.
First Example
Suppose you are an application developer tasked with diagnosing the cause of some system performance problems. Instead of using a profiling tool, we'll include a simple profiling aspect to quickly get some performance metrics. Immediately after this, you can apply a more nuanced profiling tool to that specific area.
@EnableLoadTimeWeaving
annotation
as an alternative to <context:load-time-weaver/>
.
The following example shows the less fancy aspect of profiling. This is a time-controlled profiler that uses the @AspectJ style of declaring aspects:
package foo;
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 * foo..*.*(..))")
public void methodsToBeProfiled(){}
}
package foo
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 * foo..*.*(..))")
fun methodsToBeProfiled() {
}
}
We also need to create a META-INF/aop.xml
file to tell the AspectJ binding tool that we want to
bind our ProfilingAspect
to our classes. This file convention, namely having a file (or files) in the
Java classpath called META-INF/aop.xml
, is an AspectJ standard. The following example shows the aop.xml
file:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- we only bundle classes in our application-specific packages -->
<include within="foo.*"/>
</weaver>
<aspects>
<!-- we connect only this aspect... -->
<aspect name="foo.ProfilingAspect"/>
</aspects>
</aspectj>
Now we can move on to the Spring-specific part of the configuration. We need to configure
LoadTimeWeaver
(explained later). This load-time binding tool is the main component responsible for
binding aspect configuration in one or more META-INF/aop.xml
files to your application classes.
Luckily, it doesn't require complicated setup (there are a few more options that can be set, but these will be
covered in detail later), as you can see from 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">
<!-- service object; we will profile its methods -->
<bean id="entitlementCalculationService"
class="foo.StubEntitlementCalculationService"/>
<!-- this enables binding at boot time -->
<context:load-time-weaver/>
</beans>
Now that all the necessary artifacts (aspect, META-INF/aop.xml
file and configuration Spring) in
place, we can create the following driver class with a main(..)
method to demonstrate the LTW mechanism
in action:
package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);
EntitlementCalculationService entitlementCalculationService =
(EntitlementCalculationService) ctx.getBean("entitlementCalculationService");
// the profiled aspect "weaves" the execution of this method
entitlementCalculationService.calculateEntitlement();
}
}
package foo
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
val entitlementCalculationService = ctx.getBean("entitlementCalculationService") as EntitlementCalculationService
// the profiled aspect "weaves" the execution of this method
entitlementCalculationService.calculateEntitlement()
}
We have only one thing left to do. The introduction to this section stated that with Spring you can enable LTW
selectively on a per loader class basis, and this is true. However, in this example we use the Java agent (provided
with Spring) to enable the LTW mechanism. We use the following command to run the Main
class shown
earlier:
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
Flag -javaagent
is a flag for setting and activating agents
for instrumenting programs running on the JVM. The Spring Framework includes an agent, InstrumentationSavingAgent
,
packaged in spring-instrument.jar
, which was provided as the value of the -javaagent
argument in the previous example.
The output of the Main
program looks something like the
following example. (I introduced a Thread.sleep(..)
statement into the
calculateEntitlement()
implementation so that the profiler would actually capture some value other than
0 milliseconds ( 01234
milliseconds are not the latency introduced by AOP) The following listing shows
the result we got when we ran our profiler:
Calculating entitlement StopWatch 'ProfilingAspect': running time (millis) = 1234 ------ ----- ---------------------------- ms % Task name ------ ----- ---------------------------- 01234 100% calculateEntitlement
Because this LTW mechanism is implemented using full-featured AspectJ, we are not limited to just providing
advice to Spring beans. The following slight variation on the Main
program gives the same result:
package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("beans.xml", Main.class);
EntitlementCalculationService entitlementCalculationService =
new StubEntitlementCalculationService();
// the profiled aspect will "weave" the execution of this method
entitlementCalculationService.calculateEntitlement();
}
}
package foo
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main(args: Array<String>) {
ClassPathXmlApplicationContext("beans.xml")
val entitlementCalculationService = StubEntitlementCalculationService()
// the profiled aspect will "weave" the execution of this method
entitlementCalculationService.calculateEntitlement()
}
Notice that in the previous program we load the Spring container and then create a new instance of StubEntitlementCalculationService
completely outside the Spring context. Profiled tips are still linked.
Of course, the example is simplified. However, the basics of Spring's LTW support were presented in the previous example, and the rest of this section details the meaning of each configuration bit and application.
The ProfilingAspect
used in this example may be basic, but
it is quite useful. This is a good example of a design-time aspect that developers can use during development
and then easily exclude from builds of an application deployed to a user acceptance testing (UAT) or production
environment.
Aspects
The aspects you use in LTW must be AspectJ aspects. You can write them either in the AspectJ language itself, or write your own aspects in the @AspectJ style. Your aspects will then be both valid aspects of both AspectJ and Spring AOP. In addition, the compiled aspect classes must be available on the classpath.
'META-INF/aop.xml'
The AspectJ LTW framework is configured using one or more files META-INF/aop.xml
, which are found
in the Java classpath (either directly or, more typically, in jar files).
The structure and contents of this file are described in detail in the AspectJ
's LTW reference documentation. Since the aop.xml
file is 100% written in AspectJ, we will not
describe it here.
Required Libraries (JARS)
To use LTW engine support From AspectJ to the Spring Framework you will need at least the following libraries:
spring-aop.jar
aspectjweaver.jar
If you are using the instrumentation activation agent provided by Spring, you will also need:
spring-instrument.jar
Spring Configuration
A key component of Spring's LTW support is the LoadTimeWeaver
interface (in the org.springframework.instrument.classloading
package) and its many implementations that ship with the Spring distribution. LoadTimeWeaver
is
responsible for adding one or more java.lang.instrument.ClassFileTransformers
to
ClassLoader
at runtime, which opens the door to all sorts of interesting usage modes, one of which
is aspect binding at load time.
java.lang.instrument
package before
continuing. While this documentation is not comprehensive, you will at least be able to see the main interfaces
and classes (for reference as you read this section).
Configuring LoadTimeWeaver
for a specific ApplicationContext
can be as simple as
adding a single line. (Note that you will almost certainly need to use ApplicationContext
as the
Spring container - BeanFactory
is usually not sufficient since LTW engine support uses BeanFactoryPostProcessors
).
To enable LTW support in the Spring Framework, you need to configure LoadTimeWeaver
, which is
typically done using the @EnableLoadTimeWeaving
annotation, as shown below:
@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 previous configuration automatically detects and registers a number of LTW-specific infrastructure beans for
you, such as LoadTimeWeaver
and AspectJWeavingEnabler
. By default,
LoadTimeWeaver
is a DefaultContextLoadTimeWeaver
class that attempts to complement the automatically detected
LoadTimeWeaver
. The exact type of LoadTimeWeaver
that will be "detected automatically"
depends on your runtime environment. The following table shows the different implementations of
LoadTimeWeaver
:
Runtime | Implementation LoadTimeWeaver |
---|---|
Execution at Apache Tomcat |
|
Execution in GlassFish (limited to EAR deployment) |
|
|
|
Execute in WebSphere from IBM |
|
Execution in WebLogic from Oracle |
|
JVM started with |
|
Return expecting the underlying ClassLoader to follow general conventions (namely |
|
Note that the table lists only those LoadTimeWeaver
that are automatically detected when using
DefaultContextLoadTimeWeaver
. You can specify exactly which LoadTimeWeaver
implementation to use.
To specify a specific LoadTimeWeaver
using Java configuration, implement the LoadTimeWeavingConfigurer
interface
and override the getLoadTimeWeaver()
method. The following example specifies ReflectiveLoadTimeWeaver
:
@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 are using an XML-based configuration, you can specify the fully qualified class name as the value of the
weaver-class
attribute in the <context:load-time-weaver/>
element. Again, the
following example specifies 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>
LoadTimeWeaver
, which is defined and registered by the configuration, can later be retrieved from
the Spring container using the well-known name loadTimeWeaver
. Remember that
LoadTimeWeaver
exists only as a mechanism for Spring's LTW framework to add one or more ClassFileTransformers
.
The actual ClassFileTransformer
that does LTW is the ClassPreProcessorAgentAdapter
class (from the org.aspectj.weaver.loadtime
package). More details can be found in the javadoc of
the ClassPreProcessorAgentAdapter
class, since the specifics of how binding occurs is beyond the
scope of this document.
There is one last configuration attribute left to consider: the aspectjWeaving
attribute (or
aspectj-weaving
if you're using XML). This attribute controls whether the LTW mechanism is enabled
or not. It takes one of three possible values, with the default value being autodetect
if the
attribute is missing. The following table shows three possible values:
Annotation value | XML Value | Explanation |
---|---|---|
|
|
AspectJ binding is enabled, and aspect binding occurs at load time as needed. |
|
|
Boot-time binding is disabled. Aspect binding does not occur during loading. |
|
|
If the LTW framework in Spring can find at least one |
Environment-Specific Configuration
This final section contains all additional settings and configurations required when using Spring's LTW support in environments such as application servers and web containers.
Tomcat, JBoss, WebSphere, WebLogic
Tomcat, JBoss/WildFly, IBM WebSphere Application Server and Oracle WebLogic Server all provide a common ClassLoader
for applications capable of local instrumentation. Spring's native LTW can use these ClassLoader implementations
to provide binding from AspectJ. You can simply activate linking at boot time. In particular, you do not need to
modify the JVM startup script to add -javaagent:path/to/spring-instrument.jar
.
Note that in the case of JBoss you may need to disable Scanning the application server so that it does not load
classes before the application actually runs. A quick workaround is to add a file to your artifact called WEB-INF/jboss-scanning.xml
with the following content:
<scanning xmlns="urn:jboss:scanning:1.0"/>
Universal Use of Java
If you need to instrument classes in environments that are not supported by specific LoadTimeWeaver
implementations, a common solution is the JVM agent. For such cases, Spring has
InstrumentationLoadTimeWeaver
,
which requires a Spring-specific (but extremely generic) JVM agent, spring-instrument.jar
,
automatically determined by the general settings @ EnableLoadTimeWeaving
and <context:load-time-weaver/>
.
To use it, you need to start a virtual machine with an agent from Spring, specifying the following parameters for JVM:
-javaagent:/path/to/spring-instrument.jar
Note that this requires modification to the JVM startup script, which may prevent it from being used in application server environments (depending on your server and operating policies). However, in one-application-per-JVM application deployment scenarios such as standalone Spring Boot applications, you typically have to control the entire JVM setup anyway.
Additional Resources
More information about AspectJ can be found on the AspectJ website.
Eclipse AspectJby Adrian Collier et al. (Addison-Wesley, 2005) provides a comprehensive introduction and reference to the AspectJ language.
The second edition of AspectJ in Action is highly recommended. by Ramniwas Laddad (Manning, 2009) The book focuses on AspectJ, but many general AOP topics are also explored (in quite some depth).