Spring AOP (Aspect-Oriented Programming)
Architect-Level Notes on Spring AOP
What is AOP?
Aspect-Oriented Programming (AOP) allows separation of cross-cutting concerns such as:
-
Logging
-
Security
-
Transactions
-
Auditing
-
Caching
-
Monitoring
Instead of duplicating logic across classes, AOP modularizes them into reusable components called Aspects.
Why AOP?
Without AOP:
Controller → Service → Repository Each layer duplicates logging, security, transaction logic.
With AOP:
Cross-cutting concerns are applied dynamically via proxies.
AOP Core Concepts
| Term | Description | |------|------------| | Aspect | Cross-cutting logic module | | Join Point | A point in execution (method call in Spring AOP) | | Advice | Action taken at join point | | Pointcut | Expression selecting join points | | Target | Object being advised | | Proxy | Object wrapping target | | Weaving | Linking aspects with target |
Spring AOP Architecture
Spring provides a subproject, the Spring AOP, which offers a pure Java solution for defining method execution join points on the target object—the Spring beans—by employing the Proxy pattern. You can think of the proxy objects as the wrappers around the actual objects, so the features can be introduced before, after, or around the method calls of the originator objects.
Spring AOP only applies runtime weaving while creating the proxy object mentioned, so there is no need to do any processing at the compilation time of the classes.
Spring AOP uses:
-
Proxy-based weaving (Runtime)
-
Method-level join points only
Unlike AspectJ (compile-time weaving), Spring AOP works at runtime.
Proxy Mechanism
Spring creates proxies in two ways:
-
JDK Dynamic Proxy (Interface-based)
-
CGLIB Proxy (Subclass-based)
Advice Types (Very Important)
| Type | Interface | Execution Point |
|---|---|---|
Before |
MethodBeforeAdvice |
The advice gets executed before the join-point. After Returning |
AfterReturningAdvice |
The advice gets executed after the execution of the join-point finishes. After Throwing |
ThrowsAdvice |
The advice gets executed if any exception is thrown from the join-point. After (Finally) |
N/A |
The advice gets executed after the execution of the join-point whether it throws an exception or not. Around |
Spring supports 5 types of Advice.
1. @Before
Executes before method call.
@Before("execution(* com.bank.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Method called: " + joinPoint.getSignature());
}
Use cases:
-
Input validation
-
Logging
-
Security checks
2. @After
Runs after method finishes (success or exception).
@After("execution(* com.bank.service.*.*(..))")
public void after() {
System.out.println("Method executed");
}
3. @AfterReturning
Runs only if method completes successfully.
@AfterReturning(
pointcut = "execution(* com.bank.service.*.*(..))",
returning = "result"
)
public void afterReturning(Object result) {
System.out.println("Returned: " + result);
}
4. @AfterThrowing
Runs if method throws exception.
@AfterThrowing(
pointcut = "execution(* com.bank.service.*.*(..))",
throwing = "ex"
)
public void handleException(Exception ex) {
System.out.println("Exception: " + ex.getMessage());
}
5. @Around (Most Powerful)
Controls entire method execution.
@Around("execution(* com.bank.service.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long end = System.currentTimeMillis();
System.out.println("Execution time: " + (end - start));
return result;
}
Use cases:
-
Performance monitoring
-
Retry logic
-
Circuit breakers
-
Transaction-like behavior
JoinPoint Details
JoinPoint provides:
-
Method name
-
Arguments
-
Target object
-
Signature
joinPoint.getSignature().getName();
joinPoint.getArgs();
joinPoint.getTarget();
Spring AOP supports only:
-
Method execution join points The return type of the method should be void and the parameters of the method should match the parameters of the point-cut expression. For example, if the point-cut expression is execution(* com.bank.service..(..)), the method should have any return type, and it can have any number of parameters of any type.
Pointcut Expressions (Deep Dive)
Syntax:
execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)
Example patterns:
Match all service methods:
execution(* com.bank.service.*.*(..))
Match specific method:
execution(public * transfer(..))
Match annotation:
@annotation(org.springframework.transaction.annotation.Transactional)
Combine expressions:
execution(* com.bank..*(..)) && @annotation(Loggable)
Custom Annotation + AOP
Define annotation:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable { }
Aspect:
@Around("@annotation(Loggable)")
public Object logExecution(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();
}
AOP + Transactions (Important)
@Transactional works using AOP proxy internally.
Flow:
Important Rule:
Transactional method must be called from outside the bean.
Self-invocation will NOT trigger proxy.
Banking Perspective
Use AOP for:
-
Audit logging
-
Compliance tracking
-
Security enforcement
-
Performance monitoring
Avoid:
-
Complex business logic inside aspects
-
Long-running DB calls inside advice
FAANG Perspective
Use AOP for:
-
Distributed tracing
-
Metrics collection
-
Centralized logging
-
Rate limiting
Often combined with:
-
Micrometer
-
OpenTelemetry
-
Observability stacks
Performance Considerations
-
Proxy adds slight overhead
-
Avoid excessive pointcuts
-
Use specific expressions instead of wildcards
-
Heavy @Around advice may impact latency
Common Pitfalls
-
Self-invocation problem
-
Final methods not proxied (CGLIB)
-
Private methods not advised
-
Overusing AOP for business logic
-
Incorrect pointcut expression causing performance hit
Spring AOP vs AspectJ
| Feature | Spring AOP | AspectJ | |----------|-------------|----------| | Weaving | Runtime | Compile-time / Load-time | | Join Points | Method only | Method, constructor, field | | Complexity | Low | High | | Power | Moderate | Very High |
Spring AOP is sufficient for most enterprise apps.
Architect-Level Summary
Spring AOP:
-
Uses dynamic proxies
-
Enables modular cross-cutting concerns
-
Powers @Transactional internally
-
Should be used for infrastructure concerns only
-
Not a replacement for proper architecture
Banking → Controlled, minimal aspects FAANG → Observability, tracing, metrics-heavy aspects
Internal Working of Spring AOP Proxy Chain
Spring AOP creates a proxy around the target bean.
When multiple aspects exist, Spring builds an interceptor chain.
Execution order:
Each Advice becomes a MethodInterceptor internally.
Execution Stack (Around Advice example):
-
Proxy receives method call
-
InterceptorChain created
-
First interceptor executes
-
Calls proceed()
-
Next interceptor executes
-
Eventually target method invoked
-
Control unwinds back through interceptors
Internally Spring uses:
-
AdvisedSupport
-
MethodInterceptor
-
ReflectiveMethodInvocation
Key Insight (Interview):
Spring AOP = Chain of Responsibility pattern over dynamic proxy.
Self Invocation Problem (Important)
Problem:
public void outer() {
inner(); // Will NOT trigger AOP
}
@Transactional
public void inner() { }
Why?
Call does not go through proxy.
Solution:
-
Inject self bean
-
Or refactor to another service
Architect Tip:
Keep transactional boundaries at service entry level.
Load-Time Weaving (LTW)
Spring AOP is proxy-based (runtime).
AspectJ supports:
-
Compile-time weaving
-
Load-time weaving (LTW)
LTW modifies bytecode during class loading.
Architecture:
Enable LTW:
@EnableLoadTimeWeaving
Requires:
-
AspectJ weaver agent
-
JVM argument: -javaagent:aspectjweaver.jar
When to use LTW:
-
Need field-level interception
-
Need constructor interception
-
Need domain model weaving
Banking systems rarely require LTW. Large distributed observability platforms may.
OpenTelemetry + AOP Example
Goal:
Automatically trace method execution.
Custom annotation:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Traceable { }
Aspect:
@Aspect
@Component
public class TracingAspect {
private final Tracer tracer;
public TracingAspect(Tracer tracer) {
this.tracer = tracer;
}
@Around("@annotation(Traceable)")
public Object trace(ProceedingJoinPoint pjp) throws Throwable {
Span span = tracer.spanBuilder(pjp.getSignature().getName())
.startSpan();
try (Scope scope = span.makeCurrent()) {
return pjp.proceed();
} catch (Exception ex) {
span.recordException(ex);
throw ex;
} finally {
span.end();
}
}
}
This integrates with:
-
OpenTelemetry
-
Jaeger
-
Zipkin
-
Grafana Tempo
Architect Insight:
In FAANG-style systems, observability is non-negotiable.
Distributed Tracing Architecture
Trace Context Propagation:
-
traceId
-
spanId
-
parentSpanId
Headers used:
-
traceparent
-
b3
Spring Boot auto-configures this via:
-
Micrometer Tracing
-
OpenTelemetry SDK
Retry with AOP
Simple Retry Aspect:
@Around("@annotation(Retryable)")
public Object retry(ProceedingJoinPoint pjp) throws Throwable {
int attempts = 3;
while (attempts > 0) {
try {
return pjp.proceed();
} catch (Exception ex) {
attempts--;
if (attempts == 0) throw ex;
}
}
return null;
}
Better Production Option:
Use Spring Retry:
@Retryable(value = RuntimeException.class, maxAttempts = 3)
public void callExternalService() { }
Circuit Breaker with AOP (Simplified)
Concept:
Prevent cascading failures.
States:
-
CLOSED
-
OPEN
-
HALF_OPEN
Architecture:
Basic Circuit Breaker Aspect Concept:
@Around("@annotation(CircuitProtected)")
public Object circuit(ProceedingJoinPoint pjp) throws Throwable {
if (state == OPEN) {
throw new RuntimeException("Circuit Open");
}
try {
Object result = pjp.proceed();
resetFailures();
return result;
} catch (Exception ex) {
recordFailure();
throw ex;
}
}
Production-ready approach:
Use Resilience4j.
AOP in Microservices Architecture
Common cross-cutting implementations:
-
Logging
-
Metrics
-
Rate limiting
-
Security
-
Tracing
-
Retry
-
Circuit breaking
Architecture:
Performance Impact Discussion (Interview Ready)
Spring AOP overhead:
-
One proxy invocation
-
Interceptor chain
-
Reflection call
Impact:
-
Minimal for business apps
-
Significant if overused in hot paths
Best Practice:
-
Use targeted pointcuts
-
Avoid broad wildcard expressions
-
Avoid heavy logic inside @Around
When NOT to Use AOP
Avoid for:
-
Core business logic
-
Complex domain rules
-
Performance-critical tight loops
-
Cross-service orchestration
Use AOP for:
Infrastructure concerns only.
Architect-Level Summary
Spring AOP:
-
Proxy-based runtime weaving
-
Chain of interceptors
-
Powers @Transactional
-
Enables observability
-
Supports resilience patterns
-
Must be used carefully in high-scale systems
Banking: - Minimal aspects - Audit + transactions only
FAANG: - Heavy observability - Distributed tracing - Metrics + resilience patterns - Event-driven instrumentation