When multiple aspects advise the same join point, how is @Before ordering determined, and what happens if a @Before throws?
answer
- @Order / Ordered; lower value = first for @Before
- Onion: before outer→inner, after inner→outer
- Throw short-circuits: no inner before, no target
- Entered outer layers still get @After/@AfterThrowing
- Security/validation @Before = highest precedence
basics
~20 sOrder is set by @Order / Ordered on the aspects: for before advice, the lowest order value (highest priority) runs first. If a @Before throws, later before advices and the target don't run; the exception propagates like an unwound stack.
solid answer
~50 sWhen several aspects match one join point, Spring orders them by precedence — @Order annotation or the Ordered interface on each aspect; lower value = higher precedence. For @Before advice, the highest-precedence aspect runs first (its before advice is 'outermost'). Within a single aspect, the ordering of multiple advices is not guaranteed by declaration order, so put them in separate ordered aspects if order matters. If a @Before throws, the chain short-circuits: no later before advices run, the target method never executes, and the exception propagates outward — after/around advice on higher-precedence aspects can still observe it on the way out (their @After/@AfterThrowing run as the stack unwinds), because advice nests like an onion. This is why validation/security @Before is given high precedence: it fires first and can abort before cheaper concerns like logging even start on the target.
code
java · 21 linesimport org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@Aspect @Component
@Order(Ordered.HIGHEST_PRECEDENCE) // gate first
public class SecurityAspect {
@Before("execution(* com.app.api..*(..))")
public void authorize(JoinPoint jp) {
if (!currentUserAllowed())
throw new AccessDeniedException("forbidden"); // aborts: target + inner advices skipped
}
}
@Aspect @Component
@Order(100) // runs after security's @Before
public class EntryLogAspect {
@Before("execution(* com.app.api..*(..))")
public void log(JoinPoint jp) {
log.info("enter {}", jp.getSignature().toShortString());
}
}go deeper
Know @Order controls sequence and a throw stops the call.
Explain lower order = first for @Before and that a throw skips the target.
Describe the onion nesting and which after-advices still run on a throw.
Design precedence conventions (security/validation first), reason about interaction with the transaction advisor's order, and avoid intra-aspect order dependence.
## The advice chain is an onion At a single join point, all matching advices form a nested interceptor chain. Think of each aspect as a layer wrapping the target. Higher-precedence aspects are the **outer** layers. For a call: - **Before** advice runs **outer → inner** (highest precedence first). - The **target** runs in the center. - **After** advice runs **inner → outer** (highest precedence last) — the reverse. ## Determining precedence Spring orders aspects (not individual advice methods across aspects) by: - **`@Order(n)`** annotation (`org.springframework.core.annotation.Order`) on the aspect class, or - implementing **`org.springframework.core.Ordered`** and returning `getOrder()`. **Lower value = higher precedence = runs first for before advice.** `Ordered.HIGHEST_PRECEDENCE` is `Integer.MIN_VALUE`, `LOWEST_PRECEDENCE` is `Integer.MAX_VALUE`. Aspects with no explicit order are effectively unordered relative to each other — do **not** rely on class name or declaration order. ### Within one aspect If a single aspect declares multiple advices matching the same join point, their relative invocation order is **not defined by source order**. Historically Spring didn't guarantee it; even where it's influenced by declaration order in newer AspectJ, the robust design is: **if ordering matters, split the advices into separate aspects and order those.** ```java @Aspect @Component @Order(0) // runs FIRST among before advices class SecurityAspect { @Before("...") void check() { /* authz */ } } @Aspect @Component @Order(10) // runs after SecurityAspect's before class LoggingAspect { @Before("...") void log() { /* entry log */ } } ``` ## What happens when a @Before throws Throwing is `@Before`'s only way to abort. When it throws: 1. **No later before advices** (inner layers) run. 2. **The target method never executes.** 3. The exception **propagates outward** through the already-entered outer layers. Because of the onion nesting, **outer** aspects that had already run their before advice *and* have after/around handling can still react as the stack unwinds: - An outer aspect's **`@After`** (finally-style) advice **still runs**. - Its **`@AfterThrowing`** advice runs, observing the exception. - Its **`@AfterReturning`** does **not** (there was no successful return). - An outer **`@Around`** that wrapped the call sees the exception thrown from its `proceed()` and can catch/translate it. Inner aspects whose before advice never ran do **not** get after advice — they were never entered. ### Concrete trace Given Security(@Order 0) and Logging(@Order 10), both with `@Before` and `@After`, if Security's `@Before` throws: - Security `@Before` runs → throws. - Logging `@Before` never runs (inner, not reached). - Target never runs. - Security `@After`/`@AfterThrowing` — Security's before was the one that threw; whether its own `@After` runs depends on it being a separate advice at the same/enclosing layer. In practice, the exception simply propagates to the caller; only advices at *enclosing* layers relative to the throw point observe it. The safe mental model: **the throw unwinds the partially-built onion**; layers entered before the throw get their finally/after-throwing hooks, layers not yet entered get nothing. ## Design implications (why a principal cares) - **Put security and validation `@Before` at highest precedence** (`@Order(Ordered.HIGHEST_PRECEDENCE)` or a low number) so they gate the call *before* logging/metrics/transaction advice does work that would be wasted or misleading. - **Interaction with `@Transactional`:** the transaction aspect has its own default order (`Ordered.LOWEST_PRECEDENCE` by default). If a `@Before` must run inside or outside the transaction (e.g., validation that should not open a tx vs. auditing that must be transactional), set explicit orders relative to it, or configure the transaction advisor's order. - **Fail-fast placement reduces cost and side effects** — an early throw avoids opening transactions, acquiring locks, or emitting misleading entry logs. - **Avoid order-dependent logic inside one aspect;** model each ordered concern as its own aspect for deterministic, testable sequencing. - **Document orders centrally** — implicit ordering across many aspects is a maintenance hazard; a team convention (e.g., security=0, validation=100, audit=200, logging=300) keeps it legible.
- Two @Before advices are declared in the same @Aspect class. Can you rely on source order for their execution?No — relative ordering of multiple advices within one aspect is not guaranteed. If order matters, split them into separate aspects and set @Order on each.
- A high-precedence @Before throws. Does a lower-precedence aspect's @After advice run?No — the lower-precedence (inner) aspect was never entered, so its before/after advice don't run. Only layers already entered (higher precedence) observe the unwinding via @After/@AfterThrowing.
- How do you make a validation @Before run outside a @Transactional boundary?Give the validation aspect higher precedence (lower @Order) than the transaction advisor, whose default order is Ordered.LOWEST_PRECEDENCE — so validation's before advice runs before the transaction is opened.
saying these in an interview costs you the question
- Believing higher @Order value runs first (it's the opposite for before advice)
- Relying on declaration order for multiple advices inside one aspect
- Thinking a throwing @Before still lets the target or inner advices run
- Assuming inner aspects get after-advice even when their before never ran