skip to content

AspectJ Weaving & Framework Integrations

Where Spring AOP ends and real AspectJ begins: load-time weaving, compile-time weaving, and the framework features that ride on proxies. Interviewers ask so they can hear you judge when the extra build complexity is worth it.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

19

Why does calling one @Transactional method from another method in the same class sometimes leave the transaction annotation ignored?

level: juniorimportance: must knowfreq 82%

answer

  1. proxy wraps bean, this.method() skips it
  2. internal call = raw target, no advice
  3. self-inject / separate bean / AopContext
  4. AspectJ weaving is the only true fix
  5. no error, silently ignored

basics

~10 s

Spring's @Transactional works through a proxy that wraps the bean. An internal call (this.method()) bypasses the proxy, so the transaction advice never runs. Only calls coming through the injected bean reference are intercepted.

solid answer

~40 s

Declarative features like @Transactional are applied by a proxy Spring puts around your bean. When another bean calls the method, the call goes through that proxy, which starts the transaction before delegating to your real object. But when a method inside the same class calls a sibling method with plain this.method(), the call targets the raw object directly, so the proxy — and its transaction advice — is skipped entirely. This is the self-invocation problem. It applies to all proxy-based declarative annotations: @Transactional, @Cacheable, @Async, @Retryable, @Validated. Fixes: move the annotated method into a separate bean, inject the bean into itself and call through the injected reference, or use AopContext.currentProxy(). The cleanest fix is usually redesign so the entry point is called externally.

code

java · 28 lines
java
@Service
public class OrderService {

    // Called externally -> goes through proxy -> transaction ignored below anyway
    public void placeOrder(Order o) {
        // this.save(o) is a raw call on the target object:
        // the @Transactional proxy is BYPASSED, no transaction is started.
        save(o);
    }

    @Transactional
    public void save(Order o) {
        repository.insert(o); // runs WITHOUT a transaction when reached via placeOrder()
    }
}

// Fix via self-injection:
@Service
public class OrderService2 {
    @Autowired private OrderService2 self; // injected reference IS the proxy

    public void placeOrder(Order o) {
        self.save(o); // now goes through the proxy -> @Transactional applies
    }

    @Transactional
    public void save(Order o) { repository.insert(o); }
}

go deeper

for a junior

Should know the proxy exists and that internal calls can skip @Transactional.

for a middle

Should articulate this.method() targets the raw object and list the standard fixes.

for a senior

Should connect it to all proxy-based annotations and reason about which fix fits which design.

for a principal

Should weigh AspectJ weaving vs restructuring, and understand the maintenance cost of self-injection/AopContext hacks.

## The mechanism Spring implements declarative annotations such as `@Transactional`, `@Cacheable`, `@Async`, `@Retryable`, `@Validated`, and method security using **AOP proxies**. When Spring detects one of these annotations on a bean, it does not put the logic inside your class — instead it wraps your bean in a **proxy object** that shares the same interface/type. The proxy holds a reference to your real object (the *target*). Every method call that arrives at the proxy first runs the **advice** (e.g. `TransactionInterceptor` opens a transaction), then delegates to the target, then runs the after-part (commit/rollback). Crucially, **only the proxy runs the advice.** Your real object knows nothing about transactions. ## Why self-invocation breaks it When another bean has your bean injected, it holds a reference to the **proxy**. Calling `service.doWork()` goes proxy → advice → target. Good. But inside your class, when `methodA()` calls `methodB()`, the compiler emits `this.methodB()`. `this` is the **raw target object**, not the proxy. So the call goes straight to the target, skipping the proxy and all advice. `@Transactional`/`@Cacheable`/`@Async` on `methodB` are silently ignored. No error, no warning — just no transaction, no caching, no async execution. ## Fixes 1. **Restructure (preferred):** put the annotated method in a *different* bean, so the call crosses a bean boundary and goes through that bean's proxy. 2. **Self-injection:** inject the bean into itself and call `self.methodB()`. The injected reference is the proxy. ```java @Autowired private MyService self; ``` 3. **AopContext.currentProxy():** requires `@EnableAspectJAutoProxy(exposeProxy = true)` (or `exposeProxy=true` on `@EnableTransactionManagement` is not a thing — use the proxy-exposure flag). Then `((MyService) AopContext.currentProxy()).methodB()`. 4. **Switch to AspectJ load-time/compile-time weaving** (`mode = AdviceMode.ASPECTJ`): the advice is woven into the bytecode of the class itself, so even `this.methodB()` is intercepted. This is the only fix that makes self-invocation *just work*, but it needs a weaving agent. ## Related gotchas - The proxy only wraps **Spring-managed beans**. A `new MyService()` is never advised. - Proxy-based advice cannot see **private** or (for CGLIB) **final** methods. - Public constructor logic runs before advice too. ## When to care Any time a `@Transactional`/`@Cacheable`/`@Async` method mysteriously doesn't take effect, self-invocation is the first suspect.

  • Name three ways to make an internal call actually honor @Transactional.
    Move the method to a separate bean so the call crosses a proxy boundary; self-inject the bean and call through the injected (proxy) reference; use AopContext.currentProxy() with exposeProxy=true; or switch to AspectJ weaving (mode=ASPECTJ) which is the only option that intercepts this.method() directly.
  • Does self-invocation only affect @Transactional?
    No. It affects every proxy-based declarative annotation: @Cacheable/@CacheEvict, @Async, @Retryable, @Validated, and method-level @PreAuthorize/@Secured. They all rely on the proxy, so all are bypassed on internal this.method() calls.

saying these in an interview costs you the question

  • Claiming @Transactional works regardless of how the method is called
  • Saying the fix is to make the method static or private (that makes it worse)
  • Believing Spring logs a warning when self-invocation skips advice

context

open as a page

What is Spring AOP and how does it apply an aspect to a bean at runtime?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Spring AOP adds cross-cutting behavior (logging, transactions, security) by wrapping a Spring bean in a proxy. The proxy runs your advice before/after the real method call. It works only on Spring-managed beans and needs no extra build step.

open as a page

Which Spring proxy-AOP limitations does AspectJ compile-time weaving remove, and why is it able to?

level: middleimportance: must knowfreq 55%

basics

~20 s

CTW can advise final methods, private methods, constructors, and self-invoked calls, and even objects created with new. It works because ajc edits the class bytecode directly, so there is no proxy boundary that calls must pass through to be intercepted.

open as a page

Walk through everything required to enable Spring Load-Time Weaving: the annotation, aop.xml, and the Java agent. What does each piece do?

level: middleimportance: must knowfreq 40%

basics

~10 s

Add @EnableLoadTimeWeaving on a config class, provide META-INF/aop.xml naming the aspects and packages to weave, and start the JVM with a java agent (-javaagent:spring-instrument.jar or aspectjweaver.jar) so classes can be transformed as they load.

open as a page

Why does an @Transactional (or any advised) method fail to take effect when called from another method in the same class, and how do you fix it?

level: middleimportance: must knowfreq 65%

basics

~20 s

Spring AOP advice runs in the proxy that wraps the bean. An internal call like this.save() goes straight to the real object and never touches the proxy, so the advice (transaction, cache, retry) is skipped. Fix: call through the proxy or use AspectJ.

open as a page

What is compile-time weaving (CTW) in AspectJ, and how does it differ conceptually from Spring's default AOP?

level: juniorimportance: should knowfreq 35%

basics

~20 s

Compile-time weaving uses the AspectJ compiler (ajc) to merge aspect code directly into class bytecode when you build the project. Spring's default AOP instead wraps beans in runtime proxy objects, weaving nothing at compile time.

open as a page

What is Load-Time Weaving (LTW) in Spring AOP, and how does it differ from Spring's default proxy-based AOP?

level: juniorimportance: should knowfreq 35%

basics

~10 s

Load-Time Weaving inserts aspect code into class bytecode as the JVM loads each class, using a Java agent. Spring's default AOP instead wraps beans in runtime proxy objects and only advises Spring-managed beans.

open as a page

What visibility and modifier constraints must a method satisfy for proxy-based Spring annotations to advise it, and why?

level: middleimportance: should knowfreq 58%

basics

~20 s

Proxy-based advice only applies to public methods (and by default the target must be a Spring bean). With CGLIB proxies the class and method can't be final, because CGLIB subclasses the target to override methods. Private, static, and final methods aren't advised.

open as a page

What join points and targets can full AspectJ advise that proxy-based Spring AOP cannot?

level: middleimportance: should knowfreq 45%

basics

~20 s

Spring AOP can only advise public method executions on Spring beans. Full AspectJ can additionally advise constructor calls, field reads/writes, static and private methods, final classes, and plain non-Spring objects — because it rewrites bytecode instead of wrapping a proxy.

open as a page

What is post-compile (binary) weaving, and when would you use it instead of ordinary compile-time weaving?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Post-compile (binary) weaving runs ajc over already-compiled .class files or JARs instead of source. You use it to weave aspects into bytecode you didn't compile yourself — third-party libraries or a separately built module — via ajc's -inpath.

open as a page

What are the practical caveats of @Async — how does it execute, and what makes it silently not run asynchronously or lose exceptions?

level: seniorimportance: should knowfreq 55%

basics

~20 s

@Async makes a method run on a separate thread via a proxy that submits it to a TaskExecutor. It needs @EnableAsync, a public method, and an external call (self-invocation runs it synchronously). Return void or CompletableFuture/Future; exceptions from void methods are lost unless you set an AsyncUncaughtExceptionHandler.

open as a page

A @Cacheable method's cache is never hit when called from within the same service. Diagnose and give the fix you'd ship.

level: seniorimportance: should knowfreq 48%

basics

~20 s

The internal call bypasses the caching proxy, so the CacheInterceptor never runs — every call recomputes. Fix by calling the @Cacheable method through the proxy: put it in a separate bean, self-inject the bean, or use AspectJ weaving. Restructuring into a separate bean is the cleanest.

open as a page

Explain the underlying mechanism of Spring LTW: how does @EnableLoadTimeWeaving actually get bytecode transformed at class-load time?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The Java agent captures the JVM's Instrumentation handle. Spring's InstrumentationLoadTimeWeaver registers an AspectJ ClassFileTransformer through it. As each class loads, the JVM passes its bytes to that transformer, which weaves in matching advice before the class is defined.

open as a page

When would you choose Load-Time Weaving over Spring's proxy-based AOP, and what are the costs of that choice?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Choose LTW when proxies can't reach the join point: self-invocations, private/final methods, non-Spring objects, or objects created with new (e.g. @Configurable). Costs are a required Java agent, slower startup, and classloader/deployment complexity.

open as a page

Compare the weaving models: runtime proxy weaving vs AspectJ compile-time weaving vs AspectJ load-time weaving. How do you enable each?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Spring AOP weaves at runtime by creating a proxy per bean (no build change). AspectJ compile-time weaving uses the ajc compiler to rewrite bytecode when you build. AspectJ load-time weaving rewrites bytecode as classes load, using a javaagent. Later weaving = richer reach but more setup.

open as a page

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?

level: principalimportance: should knowfreq 25%

basics

~20 s

Proxy 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.

open as a page

Contrast proxy-based weaving with AspectJ weaving for Spring's declarative annotations, and explain how you'd control ordering when several (e.g. @Transactional, @Cacheable, method security, @Retryable) apply to one method.

level: principalimportance: should knowfreq 33%

basics

~20 s

Proxy-based weaving wraps beans at runtime (JDK/CGLIB) and only intercepts external calls to public methods. AspectJ weaving edits bytecode at compile/load time, so it also advises private/final methods and self-invocation. When several advisors hit one method they run as nested layers; control the nesting with each advisor's order (e.g. @EnableTransactionManagement(order=...), Ordered/@Order), lowest order = outermost.

open as a page

As an architect, how do you decide whether to stay on Spring AOP or adopt full AspectJ? What are the trade-offs?

level: principalimportance: should knowfreq 30%

basics

~20 s

Default to Spring AOP: it is simple, needs no build or JVM changes, and covers transactions, security, and method logging on beans. Switch to full AspectJ only when you genuinely need what proxies cannot do — field/constructor join points, advising non-Spring or final objects, reliable self-invocation, or lower per-call overhead — and can accept the extra build/agent complexity.

open as a page

You're introducing Spring LTW into a production, containerized Spring Boot service. What operational risks and gotchas would you flag, and how do you de-risk the rollout?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Main risks: the agent must be present in every environment (image, CI, local) or aspects silently vanish; startup slows from class scanning; classloader/ordering issues; and AspectJ semantics differ from Spring AOP. De-risk by baking the agent into the image, scoping aop.xml tightly, and verifying with -showWeaveInfo.

open as a page