AspectJ Load-Time Weaving for Spring

Spring AOP is proxy-based, using either JDK dynamic proxy or CGLIB. Spring’s Cache Abstraction, Transaction Management and Asynchronous Execution are all built upon AOP proxies.

However proxy can intercept only external method calls. Which means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual interception at runtime.

Thus, the following code will not function correctly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public Class SomeServiceImpl implements SomeService {

@Cacheable(value = "SomeCache", key = "#kind + '#' + #id")
private Object methodBase(String kind, String id) {
// ...
return result;
}

@Override
public Object methodA(String id) {
return methodBase("a", id);
}

@Override
public Object methodB(String id) {
return methodBase("b", id);
}

}

AspectJ Load-Time Weaving

Spring provides a library named spring-aspects and AdviceMode.ASPECTJ which can be used as value of mode filed for @EnableCaching, @EnableTransactionManagement and @EnableAsync annotations to support AspectJ load-time weaving. But that is not enough, AspectJ load-time weaver is required to weave aspect for target class.

AspectJ Load-Time Weaver weave target class by transforming class file bytecode using a ClassFileTransformer named ClassPreProcessorAgentAdapter from aspectjweaver.jar. This transformation can be performed either at JVM level through Instrumentation interface or at per ClassLoader level. A method with signature similar to void addTransformer(ClassFileTransformer transformer) is required to apply the transformation in class loading phase, Instrumentation support this method natively, while not all class loaders support this.

Custom class loader to apply class file transformation

Spring’s @EnableLoadTimeWeaving creates a bean named loadTimeWeaver to inject a ClassFileTransformer, which is capable to weave target class with desired apsect, to bean class loader. Unfortunately, Spring Boot does not support this approach.

Due to the fact that loadTimeWeaver is a bean and classloading is happening at bean definition parsing phase which certainly happens before bean creation phase in same application context, thus @EnableLoadTimeWeaving should be enabled in a application context which is a ancestor of the application context where target class located in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Initializer.java
package application;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import application.ApplicationConfig;
import application.web.WebConfig;

public class Initializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{ApplicationConfig.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}

}

// ApplicationConfig.java
package application;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableLoadTimeWeaving;

@Configuration
@EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
public class ApplicationConfig {
}


// WebConfig
package application.web;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc
@ComponentScan
public class WebConfig extends WebMvcConfigurerAdapter {
}

// CachingConfiguration.java
package application.web.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching(mode = AdviceMode.ASPECTJ)
public class CachingConfiguration extends CachingConfigurerSupport {

@Bean
@Override
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}

}


// build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'io.spring.gradle:dependency-management-plugin:1.0.0.RELEASE'
}
}

repositories {
jcenter()
}

apply plugin: 'io.spring.dependency-management'
dependencyManagement {
imports {
mavenBom 'io.spring.platform:platform-bom:Brussels-SR4'
}
}

apply plugin: 'java'
sourceCompatibility = 1.8

apply plugin: 'war'


dependencies {
compile "javax.servlet:javax.servlet-api"

compile 'ch.qos.logback:logback-core'
compile 'ch.qos.logback:logback-classic'

compile 'org.aspectj:aspectjweaver'

compile "org.springframework:spring-web"
compile "org.springframework:spring-webmvc"
compile "org.springframework:spring-aspects"

}

This way, bean methods annotated with @Cacheable in package application.web.** are weaved with the aspect @EnableCaching(mode = AdviceMode.ASPECTJ) introduced from library spring-aspects.

Java agent to apply class file transformation

Instrumentation has a method void addTransformer(ClassFileTransformer transformer) which can be used to apply ApectJ’s class file transformer. There are two existing java libraries, aspectjweaver and spring-instrument, provide agents to obtain instance of Instrumentation.

AspectJ java agent to apply class file transformation

You start JVM with -javaagent:/path/aspectweaver-1.8.10.jar, then the AspectJ load-time weaver use META-INF/aop.xml files located on classpath to weaving aspect to target classes. You don’t need @EnableLoadTimeWeaving here.

Spring java agent to obtain Instrumentation instance for transformation

Differ from aspectjweaver, agent provided by spring-instrument does not apply AspectJ load-time weaving by itself, it only obtain a Instrumentation. loadTimeWeaver initiated by @EnableLoadTimeWeaving can use this Instrumentation instance to apply class file transformation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// ApplicationConfig.java
package application;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableLoadTimeWeaving;

@Configuration
@EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class ApplicationConfig {
}

// WebApplication.java
package application.web;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

import application.ApplicationConfig;

@SpringBootApplication
public class WebApplication {
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(WebApplication.class);
builder.parent(ApplicationConfig.class);
builder.run();
}
}

// build.gradle
buildscript {
repositories {
jcenter()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.6.RELEASE'
classpath 'io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE'
}
}

repositories {
jcenter()
}

apply plugin: 'org.springframework.boot'

dependencies {
compile 'org.aspectj:aspectjweaver'
compile 'org.springframework:spring-aspects'

compile 'org.springframework.boot:spring-boot-devtools'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-tomcat'
compile 'org.springframework.boot:spring-boot-starter-actuator'
}

SEE ALSO: