Core Terminology
- Aspect: A modularized concern (e.g., audit logging) implemented via
@Aspect-annotated classes or XML configuration. - Join Point: A specific point in execution—most commonly a method invocation—where advice can be applied.
- Advice: Code executed at a join point. Spring supports
@Before,@AfterReturning,@AfterThrowing,@After, and@Around. - Pointcut: An expression that selects matching join points. Advice is bound to pointcuts to define *where* it runs.
- Introduction: Enables adding new interfaces and implementations to existing beans—useful for mixin-style behavior injection.
- Target Object: The original object being advised; in Spring, this is typically wrapped by a dynamic proxy.
- AOP Proxy: Either a JDK interface-based proxy or a CGLIB class-based proxy generated at runtime.
- Weaving: The process of linking aspects in to target objects to produce advised proxies.
Getting Started
1. Enable Aspect Support
Activate annotation-driven AOP either through Java config or XML:
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
// Configuration beans...
}
Or in XML:
<aop:aspectj-autoproxy/>
2. Define an Aspect
Create a class annotated with @Aspect. It behaves like a regular Spring-managed bean but gains special AOP capabilities:
@Aspect
@Component
public class LoggingAspect {
// Fields, methods, pointcuts, and advice go here
}
In XML, register it as a standard bean:
<bean id="loggingAspect" class="com.example.LoggingAspect"/>
3. Express Join Points with Pointcuts
A pointcut declaration consists of two parts: a signature (a void method) and an expression (using AspectJ syntax). For example:
@Pointcut("execution(* com.example.account.*Service.*(..))")
public void serviceLayerOperations() {}
Supported Pointcut Designators (PCDs)
execution(): Matches method executions (most widely used).within(): Restricts matching to types within specified packages.this()/target(): Filters based on proxy or target object type.@annotation(): Selects methods marked with a given annotation.@within(): Matches types annotated with a specific annotation.args(): Matches calls where arguments match declared types.bean(): Spring-specific PCD matching beans by name pattern (e.g.,bean(*Repository)).
Composing Pointcuts
Combine expressions using &&, ||, and !:
@Pointcut("execution(* com.example.web.*Controller.*(..))")
public void webControllers() {}
@Pointcut("execution(* com.example.service.*Service.*(..))")
public void serviceMethods() {}
@Pointcut("webControllers() || serviceMethods()")
public void controllerOrService() {}
Common Pointcut Patterns
// All public methods in any package under 'com.example'
@Pointcut("execution(public * *(..))")
// Any setter method
@Pointcut("execution(* set*(..))")
// Methods in repository interfaces or implementations
@Pointcut("within(com.example.repository..*)")
// Beans whose ID ends in 'Service'
@Pointcut("bean(*Service)")
// Methods annotated with @Secured
@Pointcut("@annotation(org.springframework.security.access.annotation.Secured)")
4. Attach Advice to Pointcuts
Before Advice
Executes before the matched join point:
@Before("serviceLayerOperations()")
public void logEntry() {
System.out.println("Entering service method...");
}
After Returning Advice
Runs only if the method completes successfully. Optionally captures return value:
@AfterReturning(
pointcut = "serviceLayerOperations()",
returning = "result"
)
public void logSuccess(Object result) {
System.out.println("Method succeeded with result: " + result);
}
After Throwing Advice
Triggers when an exception is thrown:
@AfterThrowing(
pointcut = "serviceLayerOperations()",
throwing = "error"
)
public void handleFailure(RuntimeException error) {
System.err.println("Operation failed: " + error.getMessage());
}
After (Finally) Advice
Always executes—ideal for cleanup:
@After("serviceLayerOperations()")
public void releaseResources() {
System.out.println("Releasing resources...");
}
Around Advice
Most powerful advice type—it wraps the join point and controls whether and how it proceeds:
@Around("serviceLayerOperations()")
public Object profileExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
try {
Object result = joinPoint.proceed(); // Proceeds to actual method
long duration = System.nanoTime() - start;
System.out.printf("Executed in %.2f ms%n", duration / 1_000_000.0);
return result;
} catch (Throwable t) {
long duration = System.nanoTime() - start;
System.err.printf("Failed after %.2f ms: %s%n", duration / 1_000_000.0, t.getMessage());
throw t;
}
}
5. Introduce New Capabilities with Introduction
Add interfaces and default implementations to existing beans using @DeclareParents:
@Aspect
@Component
public class MonitoringAspect {
@DeclareParents(
value = "com.example.service.*+",
defaultImpl = DefaultMetricsCollector.class
)
public static MetricsCollector metricsCollector;
@Before("serviceLayerOperations() && this(metricsCollector)")
public void trackInvocation(MetricsCollector collector) {
collector.recordInvocation();
}
}
In this example, all service beans automatically gain MetricsCollector functionality without modifying their source code.