How to Solve the Self-Invocation Problem?
4. Avoid AopContext.currentProxy() 5. AspectJ - requires additional weaving setup (compile-time or load-time), complicates debugging. Use only if other solutions do not fit. 6....
Junior Level
Solution: Call the method through another bean!
// Self-invocation
@Service
public class UserService {
public void outer() {
inner(); // Around Proxy!
}
@Transactional public void inner() { }
}
// Separation into two beans
@Service
public class UserService {
@Autowired private InnerService innerService;
public void outer() {
innerService.inner(); // Through Proxy!
}
}
@Service
public class InnerService {
@Transactional public void inner() { }
}
Middle Level
Solution 1: Separation (best)
// Extract logic into a separate bean
@Service
public class OrderService {
@Autowired private NotificationService notifier;
public void createOrder() {
repository.save(order);
notifier.send(order); // Through Proxy of another bean!
}
}
Solution 2: ObjectProvider
@Service
public class MyService {
private final ObjectProvider<MyService> selfProvider;
public void outer() {
selfProvider.getObject().inner(); // Through Proxy!
}
@Transactional
public void inner() { }
}
Solution 3: TransactionTemplate
@Service
public class MyService {
private final TransactionTemplate transactionTemplate;
public void outer() {
transactionTemplate.execute(status -> {
return inner(); // In transaction without Proxy!
});
}
// If inner() returns void, use:
// transactionTemplate.executeWithoutResult(status -> { inner(); });
public void inner() { }
}
Solution 4: @Lazy Self-Injection
@Service
public class MyService {
@Autowired @Lazy
private MyService self; // Proxy of itself!
public void outer() {
self.inner(); // Through Proxy!
}
@Transactional
public void inner() { }
}
Senior Level
Why @Lazy Works
@Lazy -> Spring creates a Proxy stub
-> On first call, Proxy initializes the real bean
-> self = Proxy -> inner() goes through Proxy!
AopContext.currentProxy() (Not Recommended!)
// Fragile code, tied to Spring
((MyService) AopContext.currentProxy()).inner();
// Requires: exposeProxy = true
// -> Additional overhead
// -> Do not use!
// Problems with AopContext.currentProxy():
// (1) requires exposeProxy = true in configuration,
// (2) adds ThreadLocal overhead on every call,
// (3) code is tightly coupled to Spring AOP, hard to test.
AspectJ Weaving
LTW (Load-Time Weaving):
-> Java Agent weaves aspects on class load
-> Self-invocation works!
CTW (Compile-Time Weaving):
-> Maven/Gradle plugin weaves at compile time
-> Complex setup
-> Complicates debugging
Production Experience
Real scenario: ObjectProvider in legacy
Large service with self-invocation
-> Refactoring too expensive
-> ObjectProvider solved the problem in 10 minutes
Best Practices
- Separation -> best solution
- TransactionTemplate -> targeted control
- ObjectProvider + @Lazy -> for legacy
- Avoid AopContext.currentProxy()
- AspectJ - requires additional weaving setup (compile-time or load-time), complicates debugging. Use only if other solutions do not fit.
- Code Review -> check for self-invocation
Summary for Senior
- Separation -> clean architecture
- ObjectProvider -> modern approach
- TransactionTemplate -> no proxy problems
- @Lazy self-injection -> for legacy
- AopContext -> do not use
- AspectJ -> complex, but solves
Interview Cheat Sheet
Must know:
- 4 main solutions: (1) Split into different beans, (2) ObjectProvider, (3) TransactionTemplate, (4) @Lazy self-injection
- Bean separation is the best solution from a clean architecture perspective
- ObjectProvider.getObject() - gets a proxy of itself, modern approach
- TransactionTemplate - programmatic transaction control, no proxy problems
- @Lazy self-injection - Spring creates a Proxy stub, initializes real bean on first call
- AopContext.currentProxy() - NOT RECOMMENDED: requires exposeProxy=true, ThreadLocal overhead, Spring coupling
- AspectJ Weaving (LTW/CTW) - solves the problem, but complex setup and debugging
Common follow-up questions:
- Why does @Lazy work? -> Spring defers initialization, injects a Proxy stub which delegates to the real bean on first call
- When to choose TransactionTemplate? -> When you need a targeted transaction inside a method, without creating a separate bean
- Why is AopContext.currentProxy() bad? -> (1) exposeProxy=true, (2) ThreadLocal overhead, (3) hard coupling to Spring, (4) hard to test
- What is better: ObjectProvider or @Lazy? -> ObjectProvider - more explicit and testable; @Lazy - shorter, but less obvious
Red flags (DO NOT say):
- “AopContext.currentProxy() is the recommended solution” - not recommended
- “AspectJ is not needed, Spring AOP is always enough” - for self-invocation without refactoring, AspectJ is an option
- “TransactionTemplate = the same as @Transactional” - no, it is programmatic control, without proxy
- “@Lazy creates a new bean” - creates a proxy stub to the same bean
Related topics:
- [[18. Why Transactional does not work with self-invocation]]
- [[17. What does the Transactional annotation do]]
- [[13. What is a proxy in Spring]]
- [[12. Difference between singleton and prototype scope]]