skip to content

Why do @Autowired, @PostConstruct, and AOP proxies work in an ApplicationContext but not in a bare BeanFactory? Explain the BeanPostProcessor role.

level: seniorimportance: should knowfreq 40%

answer

  1. @Autowired/@PostConstruct/AOP = BPPs
  2. registerBeanPostProcessors() in refresh()
  3. BPPs instantiated first, wrap all later beans
  4. Raw BeanFactory: no auto-detection
  5. postProcessAfterInitialization returns proxy

basics

~20 s

Those features are implemented by BeanPostProcessors. ApplicationContext automatically detects and registers BeanPostProcessor beans during startup; a raw BeanFactory does not, so annotation injection, lifecycle callbacks, and proxying silently don't happen unless you register the processors yourself.

solid answer

~30 s

Annotation-driven injection (`@Autowired`), lifecycle callbacks (`@PostConstruct`/`@PreDestroy`), and AOP proxying are not built into the core container — they're implemented by `BeanPostProcessor` (BPP) implementations like `AutowiredAnnotationBeanPostProcessor`, `CommonAnnotationBeanPostProcessor`, and `AnnotationAwareAspectJAutoProxyCreator`. During `refresh()`, ApplicationContext runs `registerBeanPostProcessors()` which scans bean definitions for BPPs, instantiates them first, and registers them so they wrap every subsequently created bean. A **raw `DefaultListableBeanFactory`** does none of this auto-detection — BPPs sit as ordinary beans that never get applied, so annotations are ignored and no proxies are created. To get the same behavior you'd manually call `beanFactory.addBeanPostProcessor(...)` or use `AnnotationConfigUtils.registerAnnotationConfigProcessors(...)`. This is the concrete reason ApplicationContext is the practical choice.

code

java · 16 lines
java
// A custom BeanPostProcessor: auto-detected by ApplicationContext because it's a @Component
@Component
class TimingBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String name) {
        if (bean instanceof MyService) {
            // could return a proxy here (how AOP works)
            System.out.println("initialized: " + name);
        }
        return bean; // returning a wrapper/proxy is allowed
    }
}

// Raw BeanFactory equivalent — you must register processors yourself:
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
AnnotationConfigUtils.registerAnnotationConfigProcessors(bf); // now @Autowired etc. work

go deeper

for a junior

It's enough to know ApplicationContext turns on annotation processing that a bare BeanFactory doesn't; details optional.

for a middle

Name BeanPostProcessor as the mechanism and that ApplicationContext auto-registers them while a raw factory doesn't.

for a senior

Name the concrete BPP classes, place registerBeanPostProcessors() in the refresh() order, and explain proxy creation in postProcessAfterInitialization.

for a principal

Discuss BPP ordering, the early-instantiation caveat for BPP dependencies, BFPP vs BPP phases, and how to build custom container extensions safely.

## BeanPostProcessors: the extension mechanism behind the magic A **`BeanPostProcessor`** (BPP) is a container extension point with two callbacks: - `postProcessBeforeInitialization(bean, name)` — called after dependency injection but before init callbacks. - `postProcessAfterInitialization(bean, name)` — called after init callbacks; this is where AOP proxies are typically created (the returned object may be a proxy that *wraps* the original bean). Because a BPP can inspect and *replace* every bean as it's created, huge swaths of Spring are built on it rather than hardcoded into the container. ### Which features are actually BPPs - **`AutowiredAnnotationBeanPostProcessor`** — processes `@Autowired`, `@Value`, `@Inject`. Field/method/constructor injection is *this* BPP scanning metadata and injecting. - **`CommonAnnotationBeanPostProcessor`** — processes JSR-250 `@PostConstruct`, `@PreDestroy`, `@Resource`. - **`AnnotationAwareAspectJAutoProxyCreator`** (an `InstantiationAwareBeanPostProcessor`/`SmartInstantiationAwareBeanPostProcessor`) — creates AOP proxies for `@Transactional`, `@Async`, `@Cacheable`, and custom aspects. - **`ApplicationContextAwareProcessor`** — injects `ApplicationContextAware`, `EnvironmentAware`, etc. - **`PersistenceAnnotationBeanPostProcessor`** — `@PersistenceContext`. - **`ConfigurationClassPostProcessor`** is a *BeanFactoryPostProcessor* (different, earlier phase) that parses `@Configuration`/`@Bean`/`@ComponentScan`. ### How ApplicationContext wires them — the refresh() sequence `AbstractApplicationContext.refresh()` runs, in order (simplified): 1. `invokeBeanFactoryPostProcessors()` — runs `BeanFactoryPostProcessor`s (which can *modify bean definitions*), including `ConfigurationClassPostProcessor` that registers your component-scanned definitions and the annotation BPPs. 2. **`registerBeanPostProcessors()`** — finds all `BeanPostProcessor` bean definitions, instantiates them **eagerly and first** (ordered by `PriorityOrdered` > `Ordered` > the rest), and adds them to the factory so they apply to every regular bean created later. 3. `finishBeanFactoryInitialization()` — instantiates the remaining singletons, each passing through the registered BPPs. So BPPs are deliberately created *before* normal beans, which is why they can post-process everything. ### The bare BeanFactory difference A raw `DefaultListableBeanFactory` performs steps 1–2 only if *you* orchestrate them. Out of the box it does not scan for or auto-register BPPs. Consequences: - `@Autowired`/`@Value` fields stay null. - `@PostConstruct` never fires. - `@Transactional`/`@Async` are ignored (no proxy created). - `ApplicationContextAware` etc. aren't honored. To replicate ApplicationContext behavior on a raw factory you must either `addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor())` (and set its factory), or call `AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory)`. In practice nobody does this — you just use ApplicationContext. ### Gotchas - **BPPs themselves are not post-processed by other BPPs** (limited): because they're instantiated so early, a BPP's own `@Autowired` dependencies may not be fully processed the normal way — Spring even logs a warning ("is not eligible for getting processed by all BeanPostProcessors") when a BPP triggers early instantiation of other beans. Keep BPPs' dependencies minimal. - Returning a proxy from `postProcessAfterInitialization` means later injections receive the proxy, not the raw bean — the basis of self-invocation AOP pitfalls. - Ordering matters: implement `Ordered`/`PriorityOrdered` when multiple BPPs must run in sequence. - A `BeanFactoryPostProcessor` (e.g. `PropertySourcesPlaceholderConfigurer`) runs earlier and edits *definitions*, not instances — don't confuse the two. ### When it matters Understanding BPPs explains *why* ApplicationContext is mandatory for annotation-based Spring, how AOP/transactions actually attach, and is the entry point for writing your own container extensions (e.g., custom annotation injection).

  • In refresh(), which step registers BeanPostProcessors and why must it run before regular singleton instantiation?
    registerBeanPostProcessors(). It must run first so the BPPs exist and are registered when the remaining singletons are created in finishBeanFactoryInitialization — otherwise those beans wouldn't be post-processed (no injection, no proxies).
  • What's the difference between a BeanPostProcessor and a BeanFactoryPostProcessor?
    BeanFactoryPostProcessor runs earlier and modifies bean *definitions* (metadata) before any bean is instantiated (e.g. property placeholder resolution, ConfigurationClassPostProcessor). BeanPostProcessor operates on instantiated bean *instances* around their initialization, and can wrap them in proxies.

saying these in an interview costs you the question

  • Believing @Autowired is a core container feature rather than driven by AutowiredAnnotationBeanPostProcessor.
  • Saying a raw BeanFactory processes @PostConstruct/@Transactional automatically.
  • Confusing BeanPostProcessor (instances) with BeanFactoryPostProcessor (definitions).

context