Handbooks / Spring Boot / Chapter 4
Spring AOP and Proxies
7 pages · ~5 min✓ Reviewed
Builds on Building REST APIs. Next up: Spring Data JPA.
AOP Concepts and the Proxy Mental Model
1. The Vocabulary
| Term | Meaning |
|---|---|
| Aspect | @Aspect + @Component class holding logging, transactions or security, away from business code |
| Join point | A method execution on a Spring bean (no field or constructor) |
| Pointcut | Predicate that selects join points |
| Advice | Code run at those join points |
| Advisor | One pointcut paired with one advice |
2. The Proxy Mental Model
- Callergets the proxy
- Proxyruns advice
- Targetreal bean
- A BeanPostProcessor wraps the target in a proxy at startup
- Callers hold the proxy, never the target
- An aspect is a pointcut string plus an advice method
java
@Aspect @Component class Audit { @Before("execution(* com.app.service.*.*(..))") void log() { } }
3. Spring AOP vs AspectJ
| Spring AOP | AspectJ | |
|---|---|---|
| Weaving | Runtime proxies | Bytecode, compile or load time |
| Join points | Bean method executions | Field, constructor, self-call |
No proxy, no advice
Spring AOP only advises calls that cross the proxy.
Advice Types and Pointcut Expressions
1. Five Advice Types
| Advice | Runs | Changes result? |
|---|---|---|
@Before | before the method | no |
@AfterReturning(returning=) | on normal return | no |
@AfterThrowing(throwing=) | on exception | no |
@After | always, finally-style | no |
@Around | wraps the call | yes |
JDK Dynamic Proxy vs CGLIB
1. The Two Proxy Types
| JDK proxy | CGLIB | |
|---|---|---|
| Built by | java.lang.reflect.Proxy + InvocationHandler | runtime subclass, overrides methods |
| Needs interface | yes, at least one | no |
| Instance of concrete class | no | yes |
2. Which One Spring Picks
- Plain Spring: bean has interfaces → JDK, else CGLIB
- Spring Boot 2+: CGLIB by default (
spring.aop.proxy-target-class=true)
Self-Invocation: Why @Transactional and @Cacheable Get Skipped
1. The Bypass
- Advice lives in the proxy, not in the target
this.method()goes straight to the target and skips the proxy
- Callerholds proxy
- Proxyadvice runs
- outer()on target
- this.inner()no advice
java
@Transactional(propagation = REQUIRES_NEW)
public void inner() { }
public void outer() { this.inner(); }outer() is proxied, but inner() gets no REQUIRES_NEW
inner()runs with no new transaction, no rollback rules- Same cause skips
@Cacheable,@Async,@Retryable,@PreAuthorize
2. Fixes
How Proxies Power Transactions, Security and Caching
1. @Transactional
- Beginget connection
- proceed()run method
- Commitnormal return
- RollbackRuntimeException / Error
TransactionInterceptoris an @Around advice- Checked exceptions need
rollbackFor - State is bound to the thread: lost in new threads and
@Async REQUIREDjoins;REQUIRES_NEWsuspends and starts new
2. Security and Caching
| Annotation | Interceptor does | Needs |
|---|---|---|
@PreAuthorize | checks before proceed(), else AccessDeniedException |
Spring AOP Cheat Sheet
1. Advice and Pointcuts
| Advice | Runs | Changes result? |
|---|---|---|
@Before | before the method | no |
@AfterReturning | on normal return | no |
@AfterThrowing | on exception | no |
@After | always, finally-style | no |
@Around | wraps the call | yes |
Check yourself
outer() has no annotation and calls this.inner(), which is @Transactional(REQUIRES_NEW). Is inner() in a new transaction?
- No.
thisis the target, not the proxy, so the advice is skipped. - Fix: move inner() to another bean and inject it.
public void outer() { this.inner(); }An @Around advice ends with pjp.proceed(); on its own line. What does the caller get?
- The method runs, but the caller gets null: the result was never returned.
- Write
return pjp.proceed();.
A @Transactional method saves a row, then throws IOException. Rolled back?
- No, it commits. Rollback happens only for RuntimeException and Error.
- Add
rollbackFor = IOException.class.