Complete guide to Spring Aspects and AspectJ integration: compile-time and load-time weaving for advanced AOP capabilities in Spring applications.
Table of Contents
1. What is AspectJ?
AspectJ is a mature AOP framework that extends Java with aspect-oriented programming capabilities. Unlike Spring AOP (which uses proxies), AspectJ performs actual bytecode modification through weaving, enabling more powerful AOP features.
Spring integrates with AspectJ to provide compile-time and load-time weaving, allowing you to use AspectJ's advanced features while maintaining Spring's dependency injection and configuration.
2. Spring AOP vs AspectJ
2.1 Spring AOP Limitations
- Only works with Spring-managed beans
- Only intercepts method execution (not field access, constructor calls, etc.)
- Self-invocation doesn't work (calling a method on the same class)
- Runtime overhead from proxies
2.2 AspectJ Advantages
- Works with any Java class (not just Spring beans)
- Intercepts method calls, field access, constructor calls, static initializers
- Self-invocation works correctly
- No runtime proxy overhead (bytecode is modified at compile or load time)
- More powerful pointcut expressions
2.3 When to Use AspectJ
- Need to intercept field access or constructor calls
- Want to intercept calls to non-Spring-managed objects
- Self-invocation must trigger aspects
- Performance is critical (no proxy overhead)
3. Weaving Modes
3.1 Compile-Time Weaving (CTW)
Aspects are woven into classes during compilation. The compiled bytecode already contains the aspect logic.
- Pros: No runtime overhead, works with any JVM
- Cons: Requires build-time configuration, slower compilation
3.2 Load-Time Weaving (LTW)
Aspects are woven when classes are loaded by the classloader. Requires a special classloader or Java agent.
- Pros: No build-time changes, flexible
- Cons: Requires JVM agent or special classloader, slight startup overhead
3.3 Runtime Weaving (Spring AOP)
Uses dynamic proxies at runtime. No bytecode modification.
4. Compile-Time Weaving
4.1 Maven Configuration
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<complianceLevel>17</complianceLevel>
<source>17</source>
<target>17</target>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
4.2 AspectJ Aspect
package com.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + duration + "ms");
}
}
}
4.3 Gradle Configuration
plugins {
id 'java'
id 'io.freefair.aspectj.post-compile-weaving' version '8.4'
}
dependencies {
implementation 'org.springframework:spring-aspects'
aspect 'org.aspectj:aspectjrt'
}
5. Load-Time Weaving
5.1 Enable Load-Time Weaving
@SpringBootApplication
@EnableLoadTimeWeaving
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5.2 Maven Dependencies
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
5.3 JVM Agent Configuration
Add AspectJ weaver as a Java agent:
# Command line
java -javaagent:path/to/aspectjweaver.jar -jar app.jar
# application.properties
spring.instrument.classloading=true
5.4 META-INF/aop.xml
<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
<aspects>
<aspect name="com.example.aspect.PerformanceAspect"/>
</aspects>
<weaver options="-verbose">
<include within="com.example.service..*"/>
</weaver>
</aspectj>
6. AspectJ Annotations
6.1 Field Access Interception
@Aspect
@Component
public class FieldAccessAspect {
@Before("get(* com.example.model.*.password)")
public void interceptPasswordAccess() {
System.out.println("Password field accessed!");
}
@Before("set(* com.example.model.*.email)")
public void interceptEmailSet(JoinPoint joinPoint) {
System.out.println("Email field being set: " + joinPoint.getArgs()[0]);
}
}
6.2 Constructor Interception
@Aspect
@Component
public class ConstructorAspect {
@Before("execution(com.example.model.User.new(..))")
public void interceptUserCreation(JoinPoint joinPoint) {
System.out.println("Creating new User: " + Arrays.toString(joinPoint.getArgs()));
}
}
6.3 Static Method Interception
@Aspect
@Component
public class StaticMethodAspect {
@Around("execution(static * com.example.util.*.*(..))")
public Object interceptStaticMethods(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Static method called: " + joinPoint.getSignature());
return joinPoint.proceed();
}
}
6.4 Exception Handler
@Aspect
@Component
public class ExceptionHandlingAspect {
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex"
)
public void handleException(Exception ex) {
System.err.println("Exception caught: " + ex.getMessage());
// Log to monitoring system
}
}
7. Advanced Pointcuts
7.1 Pointcut Expressions
@Aspect
@Component
public class AdvancedPointcuts {
// Method execution with specific return type
@Before("execution(String com.example.service.*.*(..))")
public void interceptStringReturn() {}
// Methods with specific parameter types
@Before("execution(* com.example.service.*.*(Long, String))")
public void interceptSpecificParams() {}
// Methods in classes implementing an interface
@Before("execution(* com.example.service.Repository+.*(..))")
public void interceptRepositoryMethods() {}
// Methods with annotations
@Before("@annotation(com.example.annotation.Secured)")
public void interceptSecuredMethods() {}
// Within specific package
@Before("within(com.example.service..*)")
public void interceptServicePackage() {}
// Combined pointcuts
@Before("execution(* com.example.service.*.*(..)) && @annotation(com.example.annotation.Logged)")
public void interceptLoggedServiceMethods() {}
}
7.2 Named Pointcuts
@Aspect
@Component
public class NamedPointcuts {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Pointcut("@annotation(com.example.annotation.Transactional)")
public void transactionalMethods() {}
@Pointcut("serviceMethods() && transactionalMethods()")
public void transactionalServiceMethods() {}
@Around("transactionalServiceMethods()")
public Object aroundTransactionalService(ProceedingJoinPoint joinPoint) throws Throwable {
// Advice logic
return joinPoint.proceed();
}
}
7.3 Accessing Join Point Information
@Aspect
@Component
public class JoinPointInfoAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logJoinPointInfo(JoinPoint joinPoint) {
System.out.println("Method: " + joinPoint.getSignature().getName());
System.out.println("Target: " + joinPoint.getTarget().getClass().getName());
System.out.println("Args: " + Arrays.toString(joinPoint.getArgs()));
System.out.println("Kind: " + joinPoint.getKind());
}
}
8. Best Practices
8.1 Choosing Weaving Mode
- Use Spring AOP for simple method interception on Spring beans
- Use Compile-Time Weaving for production (no runtime overhead)
- Use Load-Time Weaving for development (faster iteration)
8.2 Performance Considerations
- CTW has no runtime overhead (aspects compiled in)
- LTW has minimal overhead (weaving at class load time)
- Spring AOP has proxy overhead on every method call
8.3 Pointcut Design
- Use specific pointcuts to avoid unintended matches
- Prefer named pointcuts for reusability
- Combine pointcuts with &&, ||, and ! operators
- Test pointcuts thoroughly to ensure correct matching
8.4 Common Use Cases
- Performance Monitoring: Measure execution time of critical methods
- Security: Intercept field access to sensitive data
- Logging: Automatic logging without code modification
- Transaction Management: Declarative transactions
- Validation: Parameter and return value validation
.png)
0 Comments