Spring AOP: The Secret Helpers Behind Your Code
The Birthday Party Story
Imagine you’re throwing a birthday party. You have the main event — eating cake, opening presents, playing games. But there’s also stuff that happens around these events:
- Someone takes photos before and after each game
- Your mom cleans up after every snack
- A helper announces when each activity starts
These helpers don’t change what you’re doing. They just add extra actions around your activities. That’s exactly what AOP (Aspect-Oriented Programming) does for your code!
What is AOP? (Aspect-Oriented Programming)
AOP is like having invisible helpers that automatically do things around your main code — without you writing it everywhere.
The Problem Without AOP
public void saveUser(User user) {
log.info("Starting saveUser"); // Repeated!
checkSecurity(); // Repeated!
// Actual work
database.save(user);
log.info("Finished saveUser"); // Repeated!
}
You write the same logging and security code in every method. Boring and messy!
The Magic of AOP
With AOP, you write logging once, and it automatically runs around all your methods:
public void saveUser(User user) {
// Just the actual work!
database.save(user);
}
// Logging happens automatically!
Core Concepts: The Party Planning Team
Think of your code as a birthday party. Here’s your team:
graph TD A[🎂 Main Code<br/>The Party] --> B[📸 Aspect<br/>The Photographer] A --> C[🧹 Aspect<br/>The Cleaner] A --> D[📢 Aspect<br/>The Announcer]
| Party Role | AOP Term | What It Does |
|---|---|---|
| Photographer | Aspect | A helper with one job (like logging) |
| “Take photo NOW” | Advice | The actual action to perform |
| “During games” | Join Point | Where you CAN add helpers |
| “Only ball games” | Pointcut | WHERE you WANT helpers |
Aspects and Join Points
What’s an Aspect?
An Aspect is a helper that handles ONE cross-cutting concern.
Example: A Logging Aspect
@Aspect
@Component
public class LoggingAspect {
// This aspect handles ALL logging
}
What’s a Join Point?
A Join Point is any place in your code where an aspect COULD jump in.
Think of it like this: Every time someone at the party does something (plays a game, eats cake, opens a present), that’s a potential moment for the photographer to take a picture.
In Spring AOP, Join Points are always method executions.
graph TD A[saveUser method] --> B[Join Point!] C[deleteUser method] --> D[Join Point!] E[findUser method] --> F[Join Point!] style B fill:#90EE90 style D fill:#90EE90 style F fill:#90EE90
AOP Advice Types: When Do Helpers Act?
Advice = The actual code that runs. But WHEN does it run?
The 5 Types of Advice
| Advice Type | When It Runs | Party Example |
|---|---|---|
| @Before | Before method | “Say cheese!” before opening present |
| @After | After method (always) | Clean up after eating cake |
| @AfterReturning | After success only | Clap when game finishes well |
| @AfterThrowing | After error only | Call mom when something breaks |
| @Around | Before AND after | Time how long each game takes |
Code Examples
@Before — Run before the method:
@Before("execution(* saveUser(..))")
public void logBefore() {
System.out.println("About to save!");
}
@After — Run after (success or fail):
@After("execution(* saveUser(..))")
public void logAfter() {
System.out.println("Save attempted!");
}
@AfterReturning — Only on success:
@AfterReturning("execution(* saveUser(..))")
public void logSuccess() {
System.out.println("Save worked!");
}
@AfterThrowing — Only on error:
@AfterThrowing("execution(* saveUser(..))")
public void logError() {
System.out.println("Save failed!");
}
@Around — Full control:
@Around("execution(* saveUser(..))")
public Object timeIt(ProceedingJoinPoint pjp)
throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // Run method
long time = System.currentTimeMillis() - start;
System.out.println("Took " + time + "ms");
return result;
}
Pointcut Expressions: Choosing WHERE
A Pointcut tells Spring: “Apply this advice HERE.”
The Pattern
execution(modifiers? return-type declaring-type? method-name(params) throws?)
Don’t worry! Let’s break it down simply:
Common Pointcut Examples
Any method named save:
@Before("execution(* save(..))")
Any method in UserService:
@Before("execution(* com.app.UserService.*(..))")
All methods in service package:
@Before("execution(* com.app.service.*.*(..))")
Methods returning String:
@Before("execution(String *(..))")
Wildcard Cheat Sheet
| Symbol | Meaning | Example |
|---|---|---|
* |
Any one thing | * save*(..) = any method starting with save |
.. |
Any arguments | save(..) = save with any params |
.. |
Any subpackages | com.app.. = app and all subpackages |
Combining Pointcuts
// AND — both must match
@Before("execution(* save*(..)) && within(com.app..*)")
// OR — either matches
@Before("execution(* save*(..)) || execution(* delete*(..))")
// NOT — exclude these
@Before("execution(* *(..)) && !execution(* get*(..))")
AOP Proxy Mechanisms: The Magic Trick
How does Spring inject code around your methods?
Spring doesn’t modify your actual code. Instead, it creates a proxy — a wrapper around your object.
graph TD A[Your Code Calls<br/>userService.save] --> B[Proxy Object] B --> C[Run @Before advice] C --> D[Call Real Method] D --> E[Run @After advice] E --> F[Return to Your Code]
Two Types of Proxies
1. JDK Dynamic Proxy (Interface-based)
- Used when your class implements an interface
- Creates a new class implementing the same interface
public interface UserService {
void save(User u);
}
public class UserServiceImpl implements UserService {
public void save(User u) { ... }
}
// Spring creates proxy implementing UserService
2. CGLIB Proxy (Class-based)
- Used when there’s no interface
- Creates a subclass of your class
public class UserService {
public void save(User u) { ... }
}
// Spring creates a subclass of UserService
Which One Does Spring Use?
graph TD A[Does class have<br/>interface?] -->|Yes| B[JDK Proxy] A -->|No| C[CGLIB Proxy] D[Spring Boot Default] --> C
Spring Boot uses CGLIB by default (even with interfaces).
Proxy Limitations: The Invisible Walls
Proxies are powerful, but they have limits. Understanding these prevents bugs!
Limitation 1: Self-Invocation Doesn’t Work
The Problem:
@Service
public class UserService {
public void processAll() {
for(User u : users) {
save(u); // DIRECT call - NO PROXY!
}
}
@Transactional // Won't work when called from above!
public void save(User u) { ... }
}
When processAll() calls save(), it’s calling directly on this — the proxy is bypassed!
graph TD A[External Call] -->|Goes through| B[Proxy ✅] B --> C[Real Object] C -->|Self call| D[Direct to save ❌] style D fill:#FFB6C1
Solution — Inject yourself:
@Service
public class UserService {
@Autowired
private UserService self; // Proxy version
public void processAll() {
for(User u : users) {
self.save(u); // Through proxy ✅
}
}
}
Limitation 2: Private Methods Can’t Be Proxied
@Transactional // Won't work!
private void saveInternal() { ... }
Proxies can only intercept public methods.
Limitation 3: Final Classes/Methods
CGLIB creates subclasses, so:
- Final classes can’t be proxied
- Final methods can’t be intercepted
public final class UserService { } // Can't proxy!
public void save() { } // ✅ Can proxy
public final void save() { } // ❌ Can't proxy
Limitation 4: Only Spring-Managed Beans
AOP only works on beans Spring knows about:
// ✅ Works — Spring manages it
@Service
public class UserService { }
// ❌ Won't work — not managed by Spring
UserService svc = new UserService();
Quick Summary
graph TD A[AOP Core Concepts] --> B[Aspect<br/>The Helper] A --> C[Join Point<br/>Where You CAN Help] A --> D[Pointcut<br/>Where You WANT to Help] A --> E[Advice<br/>What You Do] E --> F[@Before] E --> G[@After] E --> H[@Around] E --> I[@AfterReturning] E --> J[@AfterThrowing]
| Concept | Simple Definition |
|---|---|
| AOP | Adding helpers around your code |
| Aspect | A class that IS a helper |
| Join Point | Any method that CAN be helped |
| Pointcut | Expression choosing WHICH methods |
| Advice | The helper code that runs |
| Proxy | Wrapper Spring creates to inject advice |
You Did It!
You now understand how Spring magically adds logging, security, and transactions around your code without cluttering every method. Just like invisible party helpers, AOP aspects work behind the scenes to make your code cleaner and your life easier!
Remember the golden rules:
- Aspects handle cross-cutting concerns
- Pointcuts choose where to apply
- Advice defines when to run
- Proxies make it all possible
- Watch out for self-invocation and private methods!