CodeGym /Java Course /Module 5. Spring /Binding at Boot Time with AspectJ in Spring

Binding at Boot Time with AspectJ in Spring

Module 5. Spring
Level 5 , Lesson 7
Available

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.

The example presented here uses XML configuration. You can also configure and use @AspectJ using Java configuration. In particular, you can use the @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:

Java

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(){}
}
Kotlin

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:

Java

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

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:

Java

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

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.

If you are not familiar with the idea of converting class files at runtime, please refer to the API javadoc interface for the 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:

Java

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
Kotlin

@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:

Table 13. DefaultContextLoadTimeWeaver LoadTimeWeaver
Runtime Implementation LoadTimeWeaver

Execution at Apache Tomcat

TomcatLoadTimeWeaver

Execution in GlassFish (limited to EAR deployment)

GlassFishLoadTimeWeaver

Execute in JBoss AS from Red Hat or WildFly

JBossLoadTimeWeaver

Execute in WebSphere from IBM

WebSphereLoadTimeWeaver

Execution in WebLogic from Oracle

WebLogicLoadTimeWeaver

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

InstrumentationLoadTimeWeaver

Return expecting the underlying ClassLoader to follow general conventions (namely addTransformer and, optionally, the getThrowawayClassLoader method).

ReflectiveLoadTimeWeaver

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:

Java

@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {
    @Override
    public LoadTimeWeaver getLoadTimeWeaver() {
        return new ReflectiveLoadTimeWeaver();
    }
}
Kotlin

@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:

Table 14. AspectJ Binding Attribute Values
Annotation value XML Value Explanation

ENABLED

enabled

AspectJ binding is enabled, and aspect binding occurs at load time as needed.

DISABLED

disabled

Boot-time binding is disabled. Aspect binding does not occur during loading.

AUTODETECT

automatic detection

If the LTW framework in Spring can find at least one META-INF/aop.xml file, then the AspectJ binding will be started. Otherwise, linking will be disabled. This is the default value.

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

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION