Aspect-Oriented Programming in Spring: Core Concepts and Practical Implementation
Aspect-Oriented Programming (AOP) complements Object-Oriented Programming (OOP) by enabling modularization of cross-cutting concerns — behaviors that span multiple classes, such as logging, security, or transaction management. Unlike OOP, which structures code around objects and inheritance, AOP allows developers to inject shared logic without modifying core business code — aligning with Spring’s non-invasive design philosophy.
Key AOP Terminology
- Join Point: Any identifiable point during program execution — typically method invocation in Spring AOP.
- Pointcut: A predicate that matches join points. It can target a single method or a group (e.g., all methods starting with "get" or ending with "Dao").
- Advice: The action taken at a pointcut — the actual logic to be injected (e.g., timing, logging). Represented as methods within an advice clas.
- Aspect: A modular unit combining pointcuts and advice.
Annotation-Based AOP Setup
Add the AspectJ Weaver dependency:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
Define a sample DAO interface and implementation:
public interface DataRepository {
void create();
void modify();
void remove();
int fetch();
}
@Repository
public class DataRepositoryImpl implements DataRepository {
public void create() {
for (int i = 0; i < 10000; i++) {
System.out.println("Creating record...");
}
}
public void modify() { System.out.println("Modifying record..."); }
public void remove() { System.out.println("Removing record..."); }
public int fetch() { return 42; }
}
Create an aspect class to measure execution time:
@Component
@Aspect
public class PerformanceMonitor {
@Pointcut("execution(* com.example.repo.DataRepository.*(..))")
private void repositoryMethods() {}
@Around("repositoryMethods()")
public Object benchmark(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
System.out.println("Execution took: " + duration + "ms");
return result;
}
}
Enable AOP in you're configuration class:
@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy
public class AppConfig {}
AOP Proxy Workflow
- Spring container initializes and scans for aspects.
- For each bean, Spring checks if any methods match defined pointcuts.
- If matched, Spring creates a proxy wrapping the original (target) object.
- Method calls on the proxy trigger both the original logic and the advice.
Pointcut Expression Syntax
Format: execution([modifiers] [return-type] [package].[class].[method]([params]) [throws])
Examples with wildcards:
execution(* com.example.*.UserService.find*(..))— Matches any find-prefixed method in UserService under com.example.execution(* com..*.Service+.*(..))— Matches all methods in Service interfaces or their subclasses.
Advice Types
- @Before — Runs before the target method.
- @After — Runs after the target method, regardless of outcome.
- @Around — Wraps the method, allowing control over execution and return values (most powerful).
- @AfterReturning — Runs only if the method completes successfully.
- @AfterThrowing — Runs only if the method throws a exception.
Accessing Runtime Data in Advice
Use JoinPoint or ProceedingJoinPoint to access method arguments:
@Before("repositoryMethods()")
public void logArgs(JoinPoint jp) {
System.out.println("Arguments: " + Arrays.toString(jp.getArgs()));
}
Capture return values via @AfterReturning:
@AfterReturning(pointcut = "repositoryMethods()", returning = "result")
public void logResult(Object result) {
System.out.println("Returned: " + result);
}
Handle exceptions with @AfterThrowing:
@AfterThrowing(pointcut = "repositoryMethods()", throwing = "ex")
public void logException(Throwable ex) {
System.out.println("Exception: " + ex.getMessage());
}
Practical Example: Input Sanitization
Automatically trim whitespace from string parameters:
@Around("execution(* com.example.service.*.*(..))")
public Object sanitizeStrings(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof String) {
args[i] = ((String) args[i]).trim();
}
}
return pjp.proceed(args);
}