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.
answer
- proxy = runtime wrap, external+public only
- AspectJ = bytecode weave, private/final/self-invocation OK
- onion: lowest order = outermost
- retry OUTSIDE tx (fresh tx per attempt)
- security outermost by default
basics
~20 sProxy-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.
solid answer
~50 sProxy-based weaving is Spring's default: at runtime it creates a JDK dynamic proxy (interface) or CGLIB subclass around each bean. It only intercepts calls that arrive through the proxy — external calls to public, non-final methods — so it suffers self-invocation and visibility limits. AspectJ weaving (mode=ASPECTJ) instead modifies the class bytecode at compile time (CTW) or class-load time (LTW via spring-instrument agent); advice lives in the method itself, so private/final/internal calls are all advised and self-invocation disappears — at the cost of a weaver setup. When multiple declarative features apply to one method, each is a separate Advisor and they compose as nested interceptors ordered by their Ordered value. Method security and @Transactional and @Cacheable and @Retryable each expose an order attribute (e.g. the @EnableX order, or the advisor's setOrder). Lowest order = outermost/first. Typical desired nesting: security (outermost) → retry → transaction → cache, but you must set orders explicitly since defaults can surprise (e.g. retry-inside-tx vs tx-inside-retry drastically changes rollback behavior).
code
java · 20 lines// Enable features with explicit ordering so the interceptor nesting is deterministic.
@Configuration
@EnableMethodSecurity // security interceptor: very low order -> outermost
@EnableRetry(order = 0) // retry OUTSIDE tx: each attempt = fresh transaction
@EnableTransactionManagement(order = 100)
@EnableCaching(order = 200) // cache innermost here
public class AopConfig { }
@Service
public class PaymentService {
@PreAuthorize("hasRole('OPS')") // checked first (outermost)
@Retryable(retryFor = TransientException.class, maxAttempts = 3)
@Transactional // fresh tx per retry attempt
public Receipt charge(PaymentRequest req) {
// If this throws TransientException, the tx rolls back, then retry
// starts a brand-new transaction -- NOT a doomed rollback-only one.
return gateway.charge(req);
}
}go deeper
Not expected to answer; may know proxies wrap beans.
Should know proxy vs AspectJ exists and that AspectJ fixes self-invocation.
Should explain the weaving trade-offs and that stacked advisors nest by order.
Should design correct ordering (retry-outside-tx, security-outermost), justify AspectJ only when warranted, and know the LTW operational cost.
## Two weaving models ### Proxy-based (default) At container startup, `AbstractAutoProxyCreator` post-processes each bean and, if any advisor matches, replaces it with a **proxy**: - **JDK dynamic proxy** — implements the bean's interfaces (public interface methods only). - **CGLIB** — a generated subclass overriding methods (needs non-final class/methods). Characteristics: - Only **external calls through the proxy** are advised. **Self-invocation** (`this.m()`) and **private/final/static** methods are not. - Zero build setup; pure runtime. - Slight per-call indirection cost. ### AspectJ weaving (`mode = AdviceMode.ASPECTJ`) The advice is woven into the **bytecode of the class itself**: - **CTW** (compile-time weaving) via the AspectJ compiler/Gradle-Maven plugin. - **LTW** (load-time weaving) via a class-loading agent — `-javaagent:spring-instrument.jar` plus `@EnableLoadTimeWeaving` / `META-INF/aop.xml`. Characteristics: - Advises **private, protected, final** methods and **self-invocation** — because there is no proxy boundary; the logic is inside the method. - Needs weaver configuration; more moving parts, harder to debug. - Used when self-invocation/visibility genuinely can't be designed away. You enable per-feature: `@EnableTransactionManagement(mode = ASPECTJ)`, `@EnableCaching(mode = ASPECTJ)`, `@EnableAsync(mode = ASPECTJ)`. It requires the corresponding aspect on the classpath (`spring-aspects`). ## Multiple advisors on one method — how they compose When a method matches several advisors, Spring builds an **interceptor chain**. Conceptually they nest like an onion: the **first** (lowest `order`) advisor is the **outermost** layer — it runs its "before" first and its "after" last, wrapping all the inner ones. `org.springframework.core.Ordered` (lower value = higher precedence = outer) governs the sequence; ties are undefined, so set orders explicitly when it matters. Each declarative feature exposes an order: - `@EnableTransactionManagement(order = ...)` - `@EnableCaching(order = ...)` - `@EnableMethodSecurity` / method-security interceptor order (very low by default so security is outermost) - `@EnableRetry` / `@Retryable` — the `RetryOperationsInterceptor` order - `@Validated` — `MethodValidationInterceptor` Default orders: method security uses a very low order to sit outermost; transaction and caching default to `Ordered.LOWEST_PRECEDENCE` (so their relative order is unspecified unless you set it). ## Ordering that actually matters — worked examples ### Transaction vs cache Usually you want **transaction outside cache** so that a value stored on cache-miss reflects committed work — actually more subtle: for `@Cacheable` on a read, cache-outside-tx avoids opening a transaction on a hit. Decide per use case. ### Retry vs transaction — the classic trap - **Retry OUTSIDE transaction** (retry order < tx order): each retry gets a **fresh transaction**. A failed attempt rolls back cleanly, then a new transaction retries. This is almost always what you want. - **Retry INSIDE transaction** (tx outermost): all attempts share ONE transaction; once it's marked rollback-only by the first failure, subsequent attempts operate in a doomed transaction and the final commit fails. Almost always wrong. So place `@Retryable`'s advisor **outer** relative to `@Transactional`. ### Security Method security should be **outermost** — you want to reject unauthorized calls before opening transactions, hitting caches, or spending retries. Its default low order achieves this. ## Self-invocation still bites all of them Regardless of ordering, in proxy mode every one of these features is skipped on internal calls. AspectJ weaving is the systemic fix if a codebase can't avoid self-invocation. ## Decision guidance - Prefer **proxy mode** + good design (public entry points, cross-bean calls). Simpler, no build magic. - Reach for **AspectJ** only when self-invocation/private-method advising is unavoidable or pervasive. - When stacking features, **set explicit orders** and reason about the nesting, especially retry-vs-transaction and security-outermost.
- Why is putting @Retryable inside (transaction outermost) usually a bug?With the transaction as the outer layer, all retry attempts share one transaction. The first failure marks it rollback-only, so every subsequent attempt runs in a doomed transaction and the eventual commit throws. You want retry outermost so each attempt starts a fresh transaction that can commit independently.
- How does lower Ordered value map to interceptor position?Lower order = higher precedence = outermost in the chain. Its 'before' logic runs first and its 'after' logic runs last, wrapping all higher-order (inner) advisors. Ties are unspecified, so set explicit orders when the nesting affects correctness.
- What does load-time weaving require operationally that proxy mode doesn't?A weaving agent at JVM start (-javaagent:spring-instrument.jar), @EnableLoadTimeWeaving and/or META-INF/aop.xml, and spring-aspects on the classpath. It complicates deployment and debugging compared to runtime proxies, which need no agent.
saying these in an interview costs you the question
- Thinking AspectJ and proxy weaving behave identically for self-invocation
- Assuming default advisor ordering is deterministic for tx vs cache
- Placing retry inside the transaction and expecting clean per-attempt rollback
- Believing higher order value = runs first