skip to content

As a BeanPostProcessor, what ordering and eligibility hazards does the auto-proxy creator introduce, and how do you reason about which beans actually get advised?

level: principalimportance: nice to knowfreq 22%

answer

  1. Proxy = postProcessAfterInitialization step
  2. 'not eligible for all BeanPostProcessors' warning
  3. BPP deps created too early -> unadvised
  4. shouldSkip infrastructure & @Aspect
  5. @Order lowest = outermost; getEarlyBeanReference for cycles

basics

~20 s

Because proxying happens in a BeanPostProcessor after init, any bean created before that processor is active — like other BeanPostProcessors and their dependencies — won't be proxied. Aspects and AOP infrastructure beans are also skipped by design.

solid answer

~40 s

The auto-proxy creator wraps beans in postProcessAfterInitialization, so a bean is only advisable if it's created after the creator is registered and runs through the full BPP chain. Beans that are BeanPostProcessors themselves, or eagerly needed by one (BeanFactoryPostProcessors, config that a BPP depends on), are instantiated early and Spring warns they're 'not eligible for getting processed by all BeanPostProcessors' — aspects silently won't apply. The creator also deliberately skips infrastructure beans (Advisors, Advices, @Aspect classes, AopInfrastructureBean) via shouldSkip. Advisor invocation order among multiple aspects follows Ordered/@Order (lowest value = outermost). For circular dependencies it proxies early via getEarlyBeanReference and tracks earlyProxyReferences to avoid double-wrapping. Practical implication: keep aspects off infrastructure, avoid making advised beans dependencies of post-processors, and don't rely on advice for beans injected during another bean's construction.

code

java · 20 lines
java
// Two aspects on the same join point -> order controls nesting
@Aspect @Component @Order(1) // outermost
class SecurityAspect {
    @Around("execution(* com.example.api..*(..))")
    Object check(ProceedingJoinPoint p) throws Throwable { return p.proceed(); }
}

@Aspect @Component @Order(2) // inner
class TxTimingAspect {
    @Around("execution(* com.example.api..*(..))")
    Object time(ProceedingJoinPoint p) throws Throwable { return p.proceed(); }
}

// HAZARD: a bean pulled in by a BeanPostProcessor is created too early
@Component
class MetricsBpp implements BeanPostProcessor {
    // Injecting an advised service here makes THAT service ineligible for
    // auto-proxying -> its @Aspect/@Transactional advice silently won't apply.
    MetricsBpp(SomeAdvisedService s) { }
}

go deeper

for a junior

Aware that not every bean is proxied.

for a middle

Know infrastructure beans are skipped and self-invocation bypasses proxies.

for a senior

Explain the 'not eligible for all BeanPostProcessors' case and @Order precedence.

for a principal

Reason end-to-end about BPP lifecycle timing, early references for cycles, construction-time capture, and diagnose silent advice failures.

## The core constraint: proxying is a post-init step `AnnotationAwareAspectJAutoProxyCreator` is a `BeanPostProcessor` (BPP). Spring applies BPPs in a defined lifecycle: 1. `BeanFactoryPostProcessor`s run first (they mutate bean *definitions*). 2. All `BeanPostProcessor`s are **instantiated and registered**. 3. Regular singletons are created; each passes through every registered BPP, and the auto-proxy creator wraps eligible ones in `postProcessAfterInitialization`. A bean can only be advised if it flows through step 3 **after** the creator exists. That produces several hazards. ### Hazard 1 — beans created too early are never proxied Any bean that must exist *before* the BPP registration completes cannot be processed by all BPPs. Common cases: - Other `BeanPostProcessor`s and `BeanFactoryPostProcessor`s. - Ordinary beans that a BPP **depends on** (they're pulled in eagerly to build the BPP). - `@Configuration` classes / `@Bean` methods invoked during BPP setup. Spring logs: *"Bean 'x' is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)."* Aspects/`@Transactional` on such beans **silently do nothing** — a nasty, log-only failure. Mitigation: make the dependency `@Lazy`, use `ObjectProvider`, or don't let advised beans be BPP dependencies. ### Hazard 2 — infrastructure is skipped on purpose `shouldSkip`/`isInfrastructureClass` excludes `Advisor`, `Advice`, `Pointcut`, `AopInfrastructureBean`, and `@Aspect` classes. So an aspect can't advise itself or another aspect, and you can't accidentally wrap the AOP machinery. This is why a pointcut like `execution(* *(..))` doesn't cause infinite proxying. ### Hazard 3 — self-injection / construction-time capture If bean A grabs bean B during A's constructor (or B is injected into a BPP), A may capture B *before* B is proxied, holding the raw target. Constructor-time use of a collaborator can therefore bypass advice even for a normally-proxied bean. ### Ordering among multiple aspects When several advisors match one join point, the interceptor chain order is set by `Ordered` / `@Order` / `PriorityOrdered` on the aspects: **lowest order value = highest precedence = outermost** (its `@Around`/`@Before` runs first, `@After` last). Within a single aspect, ordering of advices is undefined across methods, so split into separate ordered aspects when order matters. ### Circular dependencies Via `SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference`, the creator can expose a proxy for a half-built singleton so a co-dependent bean gets the proxy, not the raw object. It records the bean in `earlyProxyReferences` so `postProcessAfterInitialization` won't wrap it again. If a proxy must be created early, Spring may also warn about the bean not being fully post-processed. ### AbstractAutoProxyCreator ordering itself The creator implements `Ordered` and is typically registered with a low precedence so it runs late in the BPP chain (after other BPPs have finished initializing the bean), ensuring it wraps the fully-initialized object. ### Reasoning checklist for 'will this bean be advised?' 1. Is it a normal singleton created after context refresh (not a BPP/BFPP or their eager dependency)? 2. Is it *not* itself AOP infrastructure / an `@Aspect`? 3. Does an advisor pointcut actually match a **proxy-visible** method (public, non-final; interface method if JDK proxy)? 4. Is it invoked **through** the proxy (not via `this.` self-invocation or a raw reference captured at construction)? Only if all four hold does the advice run.

  • You added @Transactional to a service but transactions never start, with no error. What ordering-related cause would you check?
    Check whether the service is a dependency of a BeanPostProcessor/BeanFactoryPostProcessor (or injected during another bean's construction). If created before the auto-proxy creator ran, it's 'not eligible for auto-proxying' — logged at INFO — and the transactional proxy was never built.
  • Two @Around aspects match the same method. How do you make SecurityAspect wrap TimingAspect?
    Give SecurityAspect a lower @Order/Ordered value than TimingAspect. Lowest value = highest precedence = outermost, so Security's around runs first and last, with Timing nested inside.

saying these in an interview costs you the question

  • Assuming every @Transactional/@Aspect bean is always proxied regardless of lifecycle
  • Thinking @Order highest value = outermost (it's lowest)
  • Believing aspects can advise other aspects or BPPs
  • Ignoring construction-time capture of a not-yet-proxied bean

context