03 - Spring AOP Notes
A beginner-to-advanced guide to Spring AOP, aspects, proxies, advice types, pointcut expressions, join points, and ordering for Spring Professional Certification candidates. Covers Spring Framework 6 and Spring Boot 3 concepts.
Table of Contents
- AOP fundamentals
- Proxy mechanisms
- Aspect lifecycle
- Advice types
- Pointcut expressions
- Common mistakes
- Interview questions
- Cheat sheet
1. AOP fundamentals
Aspect-Oriented Programming (AOP) separates cross-cutting concerns from business logic. Cross-cutting concerns are behaviors needed across many parts of an application, such as logging, transactions, security, caching, metrics, retries, and auditing.
In Spring, AOP is commonly used to apply these concerns declaratively without duplicating code in every service method.
Core AOP terminology
| Term | Meaning |
|---|---|
| Aspect | A module that contains cross-cutting logic, usually a class annotated with @Aspect. |
| Advice | The action taken by an aspect at a specific point, such as before or after a method call. |
| Join point | A point during program execution where advice can be applied. In Spring AOP, this is usually a method execution. |
| Pointcut | An expression that selects join points where advice should run. |
| Target object | The actual bean being advised. |
| Proxy | The Spring-created object that wraps the target and applies advice. |
| Weaving | The process of applying aspects to target objects. Spring AOP performs weaving at runtime through proxies. |
| Introduction | Adding new behavior or interfaces to an existing type. |
Spring AOP scope
Spring AOP is proxy-based and primarily supports method execution join points on Spring-managed beans.
It does not advise:
- Direct field access
- Constructor execution
- Private methods
- Static methods
- Calls made through
thisinside the same class - Objects not managed by the Spring container
For full bytecode-level weaving, use AspectJ instead of Spring AOP.
2. Proxy mechanisms
Spring AOP works by creating a proxy around a target bean. Client code calls the proxy, and the proxy decides whether to run advice before delegating to the target method.
JDK dynamic proxies
JDK dynamic proxies are used when the target bean implements at least one interface.
Characteristics:
- Proxy implements the same interface as the target
- Only interface methods are proxied
- Based on
java.lang.reflect.Proxy - Preferred when programming to interfaces
Example:
public interface PaymentService {
void pay();
}
@Service
public class PaymentServiceImpl implements PaymentService {
public void pay() {
// business logic
}
}
Spring can create a JDK proxy implementing PaymentService.
CGLIB proxies
CGLIB proxies are used when there is no suitable interface or when class-based proxying is forced.
Characteristics:
- Proxy is a subclass of the target class
- Can proxy public and protected non-final methods
- Cannot proxy final classes or final methods
- Requires method calls to go through the proxy
Class-based proxying can be enabled with:
@EnableAspectJAutoProxy(proxyTargetClass = true)
or in Spring Boot:
spring.aop.proxy-target-class=true
Self-invocation problem
Advice is not applied when a method in the same class calls another advised method through this.
@Service
public class OrderService {
public void placeOrder() {
validateOrder(); // bypasses proxy
}
@Transactional
public void validateOrder() {
// transaction advice will not run during self-invocation
}
}
Fixes:
- Move the advised method to another Spring bean
- Inject the proxy of the same bean carefully if appropriate
- Use AspectJ weaving for non-proxy-based interception
3. Aspect lifecycle
Spring aspects are Spring beans. Their lifecycle is managed by the Spring container.
Typical lifecycle:
- Spring scans and creates bean definitions.
@Aspectbeans are detected by AOP infrastructure.- Target beans are created.
- Bean post-processors wrap matching target beans with proxies.
- Calls to proxied beans are intercepted at runtime.
- Advice executes according to pointcut matches and advice type.
Enabling Spring AOP
In a Spring application:
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}
In Spring Boot, AOP auto-configuration is available when spring-boot-starter-aop is on the classpath.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Aspect bean scope
Most Spring aspects are singleton beans by default.
Avoid storing request-specific, thread-specific, or method-specific mutable state in aspect fields unless the state is properly scoped or synchronized.
Bad:
@Aspect
@Component
public class AuditAspect {
private String currentUser; // unsafe shared mutable state
}
Better:
- Use method-local variables
- Use request-scoped collaborators
- Use
ThreadLocalonly when lifecycle cleanup is guaranteed - Pass data through method parameters or context objects
Advice ordering
When multiple aspects apply to the same join point, order can be controlled with @Order or by implementing Ordered.
@Aspect
@Component
@Order(1)
public class SecurityAspect {
}
@Aspect
@Component
@Order(2)
public class LoggingAspect {
}
Lower order values have higher precedence.
For @Around advice, the highest-precedence advice runs first before proceed() and finishes last after proceed().
4. Advice types
Spring supports several advice types through AspectJ-style annotations.
@Before
Runs before the matched method executes.
Use for:
- Logging request details
- Validation
- Security checks
- Metrics start markers
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Calling: " + joinPoint.getSignature());
}
}
@After
Runs after the method completes, whether it returns normally or throws an exception.
Use for:
- Cleanup
- Final logging
- Releasing resources
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("Completed: " + joinPoint.getSignature());
}
@AfterReturning
Runs only when the method returns successfully.
Use for:
- Logging return values
- Post-processing successful results
- Publishing success events
@AfterReturning(
pointcut = "execution(* com.example.service.*.*(..))",
returning = "result"
)
public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println("Returned: " + result);
}
@AfterThrowing
Runs only when the method throws an exception.
Use for:
- Error logging
- Exception metrics
- Alerting
- Auditing failures
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex"
)
public void logException(JoinPoint joinPoint, Exception ex) {
System.out.println("Failed: " + joinPoint.getSignature());
}
@Around
Wraps method execution and gives full control over whether, when, and how the target method runs.
Use for:
- Transactions
- Caching
- Retries
- Performance timing
- Conditional method execution
@Around("execution(* com.example.service.*.*(..))")
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " took " + duration + " ms");
}
}
Important rules for @Around:
- Always call
proceed()unless intentionally skipping the target method. - Return the result from
proceed()for non-void methods. - Preserve exceptions unless intentionally translating them.
- Keep advice logic small and predictable.
5. Pointcut expressions
Common pointcut designators:
| Designator | Meaning |
|---|---|
execution(...) | Matches method execution by signature. |
within(...) | Matches join points inside selected types or packages. |
this(...) | Matches based on proxy type. |
target(...) | Matches based on target object type. |
args(...) | Matches based on runtime argument types. |
@annotation(...) | Matches methods annotated with a specific annotation. |
@within(...) | Matches types annotated with a specific annotation. |
@target(...) | Matches runtime target classes annotated with a specific annotation. |
@args(...) | Matches runtime argument types annotated with specific annotations. |
bean(...) | Spring-specific designator that matches beans by name. |
Common examples
Match all methods in a package:
@Before("execution(* com.example.service.*.*(..))")
Match all methods in a package and subpackages:
@Before("execution(* com.example.service..*(..))")
Match methods annotated with a custom annotation:
@Around("@annotation(com.example.audit.Auditable)")
Match all methods in classes annotated with @Service:
@Before("@within(org.springframework.stereotype.Service)")
Reusable named pointcut:
@Aspect
@Component
public class CommonPointcuts {
@Pointcut("within(com.example.service..*)")
public void serviceLayer() {
}
}
Use named pointcut:
@Before("com.example.aop.CommonPointcuts.serviceLayer()")
public void beforeServiceCall() {
}
6. Common mistakes
Advising non-Spring objects
Spring AOP only applies to Spring-managed beans. Objects created manually with new are not proxied.
OrderService service = new OrderService(); // not managed by Spring
Use dependency injection instead.
Expecting private methods to be advised
Private methods cannot be intercepted by Spring AOP proxies because they are not called through the proxy.
Move the logic to a public method on another bean if advice is required.
Forgetting self-invocation
A method call from one method to another method in the same class bypasses the proxy.
This commonly breaks:
@Transactional@Cacheable@Async- Custom AOP annotations
Making advised methods final
CGLIB cannot override final methods, so final methods cannot be advised with class-based proxies.
Avoid final methods on Spring beans that need proxy-based behavior.
Using overly broad pointcuts
Broad pointcuts can accidentally advise controllers, repositories, configuration classes, or infrastructure beans.
Risky:
@Around("execution(* com.example..*(..))")
Better:
@Around("within(com.example.service..*)")
Swallowing exceptions in advice
Advice should not hide failures unless that behavior is explicit and documented.
Bad:
@Around("@annotation(Retryable)")
public Object retry(ProceedingJoinPoint joinPoint) {
try {
return joinPoint.proceed();
} catch (Throwable ex) {
return null; // hides the real failure
}
}
Better:
@Around("@annotation(Retryable)")
public Object retry(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
Wrong return value from @Around
For non-void methods, returning the wrong object changes application behavior.
@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.proceed();
return null; // wrong for non-void methods
}
Correct:
@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
Misunderstanding proxy type
When JDK proxies are used, injecting the concrete implementation type can fail because the proxy implements only the interface.
Prefer injecting interfaces:
private final PaymentService paymentService;
instead of:
private final PaymentServiceImpl paymentService;
Storing mutable state in singleton aspects
Aspects are usually singleton beans. Instance fields are shared across threads.
Use local variables inside advice methods unless shared state is intentional and thread-safe.
Ignoring advice order
When multiple aspects are applied, order affects behavior.
Examples:
- Security should usually run before business logging.
- Transactions should usually wrap repository or service operations.
- Metrics may need to include or exclude retry time depending on placement.
Use @Order when behavior depends on ordering.
7. Interview questions
Basic
- What is AOP?
- What problem does AOP solve?
- What is a cross-cutting concern?
- What is an aspect?
- What is advice?
- What is a join point?
- What is a pointcut?
- What is weaving?
- How does Spring AOP apply aspects?
- What is the difference between Spring AOP and AspectJ?
Proxy and runtime behavior
- What proxy mechanisms does Spring AOP use?
- When does Spring use JDK dynamic proxies?
- When does Spring use CGLIB proxies?
- Why does self-invocation bypass advice?
- Can Spring AOP advise private methods?
- Can Spring AOP advise final methods?
- What happens if a bean implements an interface and Spring creates a JDK proxy?
- Why should Spring beans often be injected by interface when using AOP?
- How can class-based proxying be enabled?
- Why does Spring AOP only work with Spring-managed beans?
Advice and pointcuts
- What is the difference between
@Beforeand@Aroundadvice? - What is the difference between
@After,@AfterReturning, and@AfterThrowing? - Why must
@Aroundadvice callproceed()? - How do you access method arguments in advice?
- How do you access the returned value in advice?
- How do you access the thrown exception in advice?
- What is the purpose of
JoinPoint? - What is the purpose of
ProceedingJoinPoint? - How do you write a pointcut for methods annotated with a custom annotation?
- How do you reuse pointcut expressions?
Design and troubleshooting
- Why might an aspect not be triggered?
- How do you avoid overly broad pointcuts?
- How do you control the order of multiple aspects?
- Why should aspects avoid mutable instance fields?
- How can AOP be used for logging?
- How can AOP be used for transactions?
- What are the risks of using AOP heavily?
- How would you debug a pointcut that is not matching?
- When would you choose AspectJ instead of Spring AOP?
- What are common production issues caused by incorrect AOP usage?
8. Cheat sheet
Key annotations
| Annotation | Purpose |
|---|---|
@Aspect | Marks a class as an aspect. |
@Component | Registers the aspect as a Spring bean. |
@EnableAspectJAutoProxy | Enables proxy-based AOP support. |
@Pointcut | Defines a reusable pointcut expression. |
@Before | Runs before a matched method. |
@After | Runs after a matched method completes or fails. |
@AfterReturning | Runs after successful method return. |
@AfterThrowing | Runs after method throws an exception. |
@Around | Wraps method execution. |
@Order | Controls aspect precedence. |
Advice execution summary
| Advice | Runs when | Can prevent target method? | Can change return value? |
|---|---|---|---|
@Before | Before method execution | No | No |
@After | After success or failure | No | No |
@AfterReturning | After successful return | No | No |
@AfterThrowing | After exception | No | No |
@Around | Around method execution | Yes | Yes |
Proxy selection
| Situation | Default proxy type |
|---|---|
| Bean implements an interface | JDK dynamic proxy |
| Bean does not implement an interface | CGLIB proxy |
proxyTargetClass = true | CGLIB proxy |
spring.aop.proxy-target-class=true | CGLIB proxy |
Pointcut patterns
| Pattern | Meaning |
|---|---|
execution(* com.example.service.*.*(..)) | Any method in direct classes under service. |
execution(* com.example.service..*(..)) | Any method in service package or subpackages. |
within(com.example.service..*) | Any join point inside service package types. |
@annotation(com.example.Loggable) | Methods annotated with @Loggable. |
@within(org.springframework.stereotype.Service) | Types annotated with @Service. |
bean(*Service) | Beans whose names end with Service. |
Quick rules
- Spring AOP is proxy-based.
- Only calls through the proxy are advised.
- Self-invocation bypasses advice.
- Private methods are not advised.
- Final methods cannot be advised by CGLIB.
- Spring AOP advises Spring-managed beans only.
- Use
@Aroundonly when you need control over execution. - Always return
proceed()result from@Aroundunless intentionally changing behavior. - Keep pointcuts narrow and explicit.
- Avoid mutable state in singleton aspects.
Minimal aspect template
@Aspect
@Component
public class MethodLoggingAspect {
@Around("within(com.example.service..*)")
public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " took " + duration + " ms");
}
}
}
Troubleshooting checklist
| Symptom | Likely cause |
|---|---|
| Aspect does not run | AOP not enabled or starter missing. |
| Aspect runs for some calls but not internal calls | Self-invocation bypassing proxy. |
| Bean injection fails with concrete class | JDK proxy created for interface. |
| Advice not applied to method | Method is private, final, static, or not called through proxy. |
| Advice not applied to object | Object was created with new, not by Spring. |
| Unexpected methods advised | Pointcut is too broad. |
| Wrong method result | @Around advice did not return proceed() result. |
| Race conditions in aspect | Mutable state stored in singleton aspect fields. |