When would you reach for AopContext.currentProxy()/exposeProxy versus self-injection, a separate bean, or AspectJ weaving? What are the trade-offs?
answer
- all fix self-invocation
- AopContext = coupling smell
- self-injection = same idea, no AOP import
- separate bean = honest design
- AspectJ = weave bytecode, no proxy
basics
~20 sAll solve the same self-invocation problem. AopContext is quickest but couples code to Spring AOP. Self-injection is cleaner and equivalent. Extracting a separate bean is the most honest design. AspectJ weaving fixes it at bytecode level so no re-routing is ever needed.
solid answer
~50 sThey all address self-invocation, where an internal call bypasses the proxy and its advice. `AopContext.currentProxy()` with `exposeProxy=true` is the most explicit and imperative, but it leaks Spring AOP into business code, needs the right cast, and only works on the proxied thread. Self-injection — autowiring the bean into itself — is the same 'call through the proxy' idea without referencing AOP classes; Spring resolves the self-reference for singletons. Extracting the advised method into a **separate collaborator bean** is usually the best design: the boundary between the two responsibilities becomes real and the proxy naturally intercepts the cross-bean call. **AspectJ** (compile-time or load-time weaving) advises the actual bytecode rather than a wrapper, so even `this` calls are intercepted and none of these workarounds are needed — at the cost of build/agent setup. Choose based on how much you want AOP visible in code and infrastructure.
code
java · 15 lines// Self-injection: cleanest 'route through the proxy' without AopContext
@Service
public class InvoiceService {
@Autowired
@Lazy // avoids constructor cycle if constructor-injected
private InvoiceService self;
public void processBatch(List<Invoice> batch) {
batch.forEach(self::finalizeOne); // advised, unlike this::finalizeOne
}
@Transactional
public void finalizeOne(Invoice i) { /* own tx per invoice */ }
}go deeper
Can name self-injection or 'split the bean' as fixes.
Compares AopContext vs self-injection and knows AspectJ exists.
Articulates coupling/testability trade-offs and picks per situation.
Sets team guidance (prefer redesign/self-injection; reserve AopContext; adopt AspectJ only when justified) and weighs build/ops cost.
## The shared root cause Every option here exists because Spring's **proxy-based AOP** can't intercept a call that doesn't cross the proxy. An internal `this.method()` call stays inside the target object, so advice (`@Transactional`, `@Cacheable`, custom aspects) is skipped. The four fixes differ in *how* they get the call to cross a proxy — or eliminate the need to. ## Option 1 — AopContext.currentProxy() + exposeProxy - **How:** enable `exposeProxy` (e.g. `@EnableAspectJAutoProxy(exposeProxy = true)`), then `((T) AopContext.currentProxy()).method()`. - **Pros:** explicit at the call site; no extra fields; works even for one-off cases. - **Cons:** imports `org.springframework.aop.framework.AopContext` into domain code (coupling/testability smell); requires a correct interface-vs-class cast; throws if the flag is off or you're on a non-proxied thread; easy to forget on future call sites. ## Option 2 — Self-injection ```java @Service public class MyService { @Autowired private MyService self; // the proxy, injected public void outer() { self.inner(); } @Transactional public void inner() {} } ``` - **How:** inject the bean into itself; `self` is the proxy, so `self.inner()` is advised. - **Pros:** no AOP-specific API in your code; readable; Spring tolerates the singleton self-reference (setter/field injection, or constructor injection with `@Lazy`). - **Cons:** still slightly surprising; a field that exists purely to defeat a proxy quirk; constructor injection needs `@Lazy` to avoid a cycle. ## Option 3 — Extract a separate bean - **How:** move `inner()` into its own `@Service` and inject that collaborator. - **Pros:** the cleanest design — the call now genuinely crosses a proxy boundary, and the split often reflects a real separation of concerns (orchestration vs unit of work). - **Cons:** more classes; only natural when the split makes domain sense (don't split arbitrarily just to please AOP). ## Option 4 — AspectJ weaving - **How:** compile-time weaving (AspectJ compiler) or load-time weaving (`-javaagent:aspectjweaver.jar` / `@EnableLoadTimeWeaving`). Advice is woven into the **actual class bytecode**, not a wrapper. - **Pros:** intercepts *all* calls including `this` self-invocations and even calls to non-Spring-managed objects; no proxies, no re-routing tricks; can advise fields/constructors. - **Cons:** build or JVM-agent complexity; steeper learning curve; behavior further from plain Java; overkill for a single stubborn method. ## Decision guide - One stubborn call, can't refactor now → self-injection (preferred) or AopContext. - The two methods are really two responsibilities → extract a bean. - Pervasive self-invocation, domain-model advice, or you need advice on non-bean objects → AspectJ weaving. ## Testability note AopContext-based code is harder to unit test because `currentProxy()` throws outside a proxy context. Self-injection and separate beans are trivially mockable, another reason they're usually preferred.
- Why is AspectJ weaving immune to the self-invocation problem?AspectJ weaves advice directly into the target class's bytecode (compile- or load-time) instead of wrapping it in a proxy. Because there's no wrapper, even internal `this` calls execute the woven advice — nothing has to route through a proxy.
- Does self-injection cause a bean creation cycle, and how is it handled?For a singleton, Spring can inject the bean into itself. Field/setter injection works out of the box; constructor injection needs @Lazy on the self reference so Spring injects a lazy proxy and breaks the cycle.
saying these in an interview costs you the question
- Claiming AspectJ has the same self-invocation limitation as Spring proxies
- Saying self-injection is impossible due to a circular dependency
- Treating AopContext as the recommended default rather than a last resort