Compare compile-time weaving, load-time weaving, and Spring proxy AOP. How would you choose among them for a service, and what does each need to be wired up?
answer
- proxy = simplest, beans+public only
- CTW = ajc build-time, full reach, fastest runtime
- LTW = aspectjweaver agent + aop.xml + @EnableLoadTimeWeaving
- reach parity CTW==LTW; cost = build vs startup
- default proxy, escalate only when join point forces it
basics
~20 sProxy AOP: simplest, no special build, but only public methods on Spring beans. CTW: ajc weaves at build time, full reach (final/private/self-call), fastest at runtime, complex build. LTW: aspectjweaver java agent weaves at class load, same reach as CTW, no ajc, but agent + slower startup.
solid answer
~50 sThree options, increasing power and cost. Spring proxy AOP is the default: no special compiler, only method-execution join points on container-managed beans, and the proxy blind spots (final/private/self-invocation). It needs nothing beyond spring-aop and @EnableAspectJAutoProxy. Compile-time weaving uses ajc via the aspectj-maven-plugin/Gradle plugin to bake advice into bytecode at build time — full AspectJ join-point reach, zero runtime weaving cost, but a heavier, AspectJ-specific build and the aspectjrt dependency. Load-time weaving defers weaving to class-loading through the aspectjweaver java agent, configured with @EnableLoadTimeWeaving plus META-INF/aop.xml; it keeps a normal javac build and gains full reach, at the price of a -javaagent (or Spring instrumentation), slower startup, and classloader sensitivity. Rule of thumb: default to proxies; pick CTW when you own the source and want maximum runtime performance/reach; pick LTW when you can't or won't change the build but still need to advise final/private/domain code.
code
xml · 12 lines<!-- Load-time weaving config: META-INF/aop.xml -->
<aspectj>
<weaver options="-Xset:weaveJavaxPackages=false">
<!-- only weave our packages, keep startup cost bounded -->
<include within="com.katajob..*"/>
</weaver>
<aspects>
<aspect name="com.katajob.aop.AuditAspect"/>
</aspects>
</aspectj>
<!-- Enabled via @EnableLoadTimeWeaving on a @Configuration class,
and run with -javaagent:aspectjweaver.jar (or a container LoadTimeWeaver). -->go deeper
Know the three exist and that proxy AOP is the default/simplest.
Match each mode to its wiring (proxy vs ajc vs aspectjweaver+aop.xml) and know CTW/LTW beat proxies on final/private/self-call.
Articulate the cost split (build time vs startup time vs runtime) and the aspectjrt-vs-aspectjweaver dependency difference.
Drive a defensible selection rule tied to correctness (not just perf), account for AdviceMode.ASPECTJ, double-advising, classloader/startup risk, and observability trade-offs.
This is a design-judgment question. Structure the answer as **capability**, **wiring**, and **cost** for each of the three, then give selection criteria. **1) Spring proxy-based AOP (default).** - *Capability*: method-`execution` join points on **Spring-managed beans** only. Cannot advise final/private/self-invoked/constructor/field join points. - *Wiring*: `spring-aop` on the classpath, `@EnableAspectJAutoProxy` (auto-on in Spring Boot), `@Aspect` beans. JDK dynamic proxy for interface beans, CGLIB otherwise (Boot defaults to CGLIB via `proxyTargetClass=true`). - *Cost*: trivial build; small per-call proxy indirection; the blind spots are the real tax. **2) Compile-time weaving (CTW).** - *Capability*: **full AspectJ** — execution, call, get/set (field), constructor/initialization, staticinitialization; advises final, private, self-invoked, static, and even `new`-created domain objects (with `@Configurable` + `@EnableSpringConfigured`). - *Wiring*: replace javac with **ajc** via `aspectj-maven-plugin` (`dev.aspectj`) or a Gradle AspectJ plugin; runtime needs **aspectjrt**; no agent, no proxy. Aspects can be `@Aspect` annotation-style or native `.aj`. - *Cost*: slower, AspectJ-specific builds; IDE/tooling friction; but **fastest runtime** (weaving already done, no per-call proxy) and largest reach. **3) Load-time weaving (LTW).** - *Capability*: same **full AspectJ reach** as CTW (final/private/self-call/field/etc.), because it also modifies bytecode — just later. - *Wiring*: keep a normal `javac` build; weave as classes load via the **aspectjweaver** java agent (`-javaagent:aspectjweaver.jar`), or Spring's `@EnableLoadTimeWeaving` with a `LoadTimeWeaver` (e.g. `InstrumentationLoadTimeWeaver`, or container weavers like Tomcat's). Requires a **`META-INF/aop.xml`** listing aspects and weaver options. Runtime needs **aspectjweaver** (which includes the runtime). - *Cost*: slower **startup** (weaving happens as each class loads), classloader-ordering sensitivity (the agent must see classes before they load), and operational complexity of shipping/enabling a java agent. No build-time ajc, though — appealing when you can't touch the build. **Selection criteria:** - **Default to proxy AOP.** It covers the overwhelmingly common case (advice on public service methods) with zero ceremony. Reserve heavier options for when a proxy demonstrably can't reach the join point. - **Choose CTW** when: you own the source; you want maximal runtime performance (hot paths where per-call proxy overhead matters, or very fine-grained join points); you must advise final/private/self-calls or domain entities; and a specialized build is acceptable. Kotlin shops hit this often because classes/methods are `final` by default. - **Choose LTW** when: you need full AspectJ reach but must keep a standard build (e.g., mixed/legacy toolchain), or want to toggle weaving per-environment via `aop.xml`, and can tolerate a java agent and startup cost. Also the go-to for instrumenting classes you can't recompile at build time. - **Binary/post-compile weaving** is the fourth flavor: CTW's mechanism applied to already-compiled JARs (`-inpath`) — pick it to advise third-party bytecode you lack source for. **Cross-cutting gotchas to mention:** - CTW and LTW can advise `@Async`/`@Transactional`/`@Cacheable` on self-invoked or final methods that proxy mode silently drops — a genuine correctness reason to switch, not just performance. - Mixing modes: Spring lets `@EnableLoadTimeWeaving` coexist, but combining AspectJ weaving with Spring proxying on the same beans can double-advise — be deliberate. - Observability: woven bytecode changes stack traces and can complicate debugging; proxies keep the original class bytecode intact. - All AspectJ modes share pointcut fragility: pointcuts over internal/private signatures break more easily on refactors than public-method pointcuts. A strong answer ends with the principle: **push complexity as late as it's cheap** — use proxies unless a concrete join point forces AspectJ, then prefer CTW for owned source / performance and LTW when the build must stay standard.
- You have a hot path where @Transactional on a final Kotlin method is silently not applied. What do you change and why?Switch that module to AspectJ weaving (CTW if you own the source and want no runtime cost, else LTW). Proxy mode can't override the final method, so the transaction advice never runs; weaving bakes it into the method bytecode so finality is irrelevant. Configure Spring's @EnableTransactionManagement(mode = AdviceMode.ASPECTJ).
- What's the runtime dependency difference between CTW and LTW?CTW needs aspectjrt (the runtime the woven code calls). LTW needs aspectjweaver (the java agent, which bundles the runtime). CTW has no agent; LTW's agent must be attached at JVM startup or via a container LoadTimeWeaver.
saying these in an interview costs you the question
- Saying LTW has less join-point reach than CTW (they're equal).
- Claiming CTW needs a java agent, or LTW needs ajc in the build.
- Treating the proxy blind spots as only a performance issue rather than a correctness one for @Transactional/@Async on self/final calls.
- Forgetting @EnableTransactionManagement/@EnableCaching have mode=ASPECTJ to route through the weaver.