Architecturally, when would you move from Spring proxy AOP to AspectJ weaving to solve self-invocation, and what are the costs?
answer
- proxy = caller-side, structural limit
- AspectJ weaves into bytecode, no proxy
- CTW (ajc) vs LTW (agent + @EnableLoadTimeWeaving)
- advises private/final too
- cost: agent/build/debug/team coupling
basics
~20 sSwitch to AspectJ when you need advice to fire on internal calls (and even private/final methods) across the codebase, not just one spot. Costs: compile-time or load-time weaving setup, a JVM agent for LTW, harder debugging, and broader coupling to AspectJ.
solid answer
~40 sProxy AOP is caller-side, so it structurally can't advise self-invocations or private/final methods. If those limitations keep biting across many components — pervasive @Transactional/@Cacheable on internal calls, or you want method-level advice you can't restructure around — AspectJ weaving is the principled fix: it weaves advice into the method bytecode itself, so there's no proxy and every call site is advised. You choose compile-time weaving (AspectJ compiler, cleanest runtime) or load-time weaving (@EnableLoadTimeWeaving + spring-aspects + a Java agent / spring-instrument). Costs: build-toolchain or JVM-agent complexity, slower builds/startup, trickier debugging and stack traces, and coupling to AspectJ across the team. For most apps I treat self-invocation as a design signal and refactor instead; I adopt AspectJ only when internal advice is a genuine cross-cutting requirement the domain can't be reshaped to avoid.
code
java · 19 lines// AspectJ load-time weaving with Spring's transaction support:
@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ) // weave, don't proxy
@EnableLoadTimeWeaving // enable LTW
class WeavingConfig {}
// Requires on the runtime classpath: spring-aspects,
// and a JVM agent, e.g. -javaagent:/path/aspectjweaver.jar
// plus META-INF/aop.xml. Then @Transactional on an internal
// this.save(o) call IS honored, because advice is woven into save()'s bytecode.
@Service
class OrderService {
public void placeOrder(Order o) {
save(o); // internal call now transactional under AspectJ weaving
}
@Transactional
public void save(Order o) { /* ... */ }
}go deeper
Not expected to reach this; awareness that AspectJ is an alternative to proxies suffices.
Knows AspectJ weaves into bytecode and thus solves self-invocation, without deep cost analysis.
Explains CTW vs LTW and the main trade-offs, and would default to refactoring.
Frames it as a platform-level decision — weighs team coupling, environment-wide agent pinning, debuggability, and treats self-invocation as a design signal first.
## The architectural decision Self-invocation isn't a bug in Spring — it's an inherent property of **proxy-based (caller-side) AOP**. The proxy only intercepts calls that arrive on the proxy reference; a target calling itself, or a `private`/`final` method, is out of reach. Two families of solution exist: **work within the proxy model** (refactor, self-inject, exposeProxy) or **abandon proxies for AspectJ weaving**. Choosing the latter is an architecture-level call, not a per-method fix. ## What AspectJ weaving does differently AspectJ is a full AOP system that **weaves advice directly into class bytecode** at the join points. There is no wrapper object — the advised behavior becomes part of the method. Consequences: - **Self-invocation is advised** just like external calls, because the advice lives in the method body. - It can advise **private and final methods**, constructors, field access, and static methods — join points proxies can't reach. - Spring's `@Transactional`/`@Cacheable`/`@Async` support an AspectJ mode (`mode = AdviceMode.ASPECTJ`) so the same annotations work. ## Two weaving strategies 1. **Compile-time weaving (CTW).** The **AspectJ compiler (`ajc`)** weaves aspects during the build. Cleanest runtime (no agent), best performance, but needs the AspectJ build plugin and compiles differently from `javac`. 2. **Load-time weaving (LTW).** Aspects are woven as classes load, via a **Java agent** (`-javaagent:aspectjweaver.jar` or Spring's `spring-instrument`) plus `@EnableLoadTimeWeaving` and a `META-INF/aop.xml`. More flexible, no special compiler, but requires a JVM agent and correct classloader/ordering — the usual source of setup pain. ## Costs and risks - **Build/deploy complexity:** an extra compiler plugin or a `-javaagent` on every JVM (local, CI, containers, prod). Easy to forget in one environment. - **Slower feedback:** weaving adds build or startup time. - **Debuggability:** woven code and altered stack traces are less obvious than a visible proxy; new team members find it surprising ('magic in the bytecode'). - **Scope discipline:** weaving is powerful and global; over-broad pointcuts can advise far more than intended, hurting performance or correctness. - **Team coupling:** everyone must understand AspectJ, not just Spring annotations. ## Decision framework - **Prefer refactoring** (extract collaborator beans) for the common case. Self-invocation is often a hint that a class is doing too much; splitting it improves the design and keeps proxy AOP. - **Use self-injection / exposeProxy** for isolated, hard-to-refactor spots. - **Adopt AspectJ** only when internal-call advice or private/final-method advice is a **genuine, recurring cross-cutting requirement** you can't design around — e.g. deep domain methods that must be transactional/cached and can't be relocated, or advanced instrumentation. Weigh it as a platform decision with the whole team, and pin the agent/plugin across all environments. ## Bottom line AspectJ definitively removes the self-invocation limitation, but it trades Spring's lightweight, transparent proxying for a heavier, more invasive weaving model. For most applications the right answer is to treat self-invocation as a design smell and refactor; AspectJ is the deliberate choice when the requirement is real and pervasive.
- What's the practical difference between compile-time and load-time weaving?CTW weaves during the build with the AspectJ compiler (ajc) — no runtime agent, best performance. LTW weaves as classes load using a JVM agent (aspectjweaver / spring-instrument) plus @EnableLoadTimeWeaving and aop.xml — more flexible but needs the agent configured in every environment.
- Why is self-invocation often better treated as a design smell than fixed with AspectJ?A class calling its own transactional/cached method usually mixes orchestration with a distinct responsibility; extracting a collaborator both fixes the advice (external call through a proxy) and improves cohesion, without the weight of AspectJ tooling.
saying these in an interview costs you the question
- Reaching for AspectJ to fix a single self-invocation spot
- Thinking AspectJ needs no special build or agent setup
- Assuming woven code debugs the same as plain code
- Not realizing @Transactional/@Cacheable have an explicit ASPECTJ advice mode
- Believing proxies could be tuned to advise private/final methods