What are the subtle edge cases and gotchas when using Aware callbacks (ordering with @PostConstruct, prototypes, doing work in the setter, self-injection)?
answer
- capture reference, act in @PostConstruct
- prototype: fires every time, no destroy callback
- bare BeanFactory → context-Aware null, no error
- getBean in setter → half-built / cycle
- Aware before AOP proxy → 'this' is raw target
basics
~20 sAware setters run once per instance, before init callbacks, so don't do heavy work in them — just capture the reference and act in @PostConstruct. Context-level Aware needs an ApplicationContext to fire, and calling getBean during the callback can hit half-built beans.
solid answer
~40 sKey gotchas: (1) Aware setters run before @PostConstruct and afterPropertiesSet, so only capture the reference there and defer real logic to an init method — the container isn't fully refreshed yet. (2) For prototype beans the callback runs on every instantiation, and Spring won't manage their destruction, so infrastructure grabbed via BeanFactoryAware must be handled carefully. (3) Context-level Aware (ApplicationContextAware/EnvironmentAware/ResourceLoaderAware) silently no-ops in a bare BeanFactory because it lacks ApplicationContextAwareProcessor. (4) Calling context.getBean(...) inside setApplicationContext can trigger or touch beans that aren't fully initialized, risking partial state or eager instantiation ordering surprises. (5) The Aware setter is invoked on the raw bean before AOP proxying by post-processors completes, so a reference to 'this' captured there is the target, not the proxy — self-invocation won't go through advice.
code
java · 22 linesimport org.springframework.context.*;
import org.springframework.beans.factory.*;
@Component
class Coordinator implements ApplicationContextAware, SmartInitializingSingleton {
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext c) {
// GOTCHA ZONE: context not fully refreshed here.
// Do NOT call c.getBean(...) or start threads. Just capture.
this.ctx = c;
}
@Override
public void afterSingletonsInstantiated() {
// Safe: every non-lazy singleton exists now, all proxies applied.
ctx.getBeansOfType(Worker.class)
.values()
.forEach(Worker::register);
}
}go deeper
Awareness only — just capture the reference and don't do heavy work in the setter.
Know Aware runs before @PostConstruct and to defer real logic to an init method.
Explain the getBean-during-init and bare-BeanFactory pitfalls and prototype lifecycle differences.
Reason about proxy-timing (raw-target capture bypassing advice), reentrancy/eager-instantiation ordering, and choose SmartInitializingSingleton/ContextRefreshedEvent for whole-context work.
## 1. Timing vs. initialization callbacks Aware setters fire during initialization **before** `@PostConstruct`, `InitializingBean.afterPropertiesSet()`, and custom init methods (context-level Aware runs in `postProcessBeforeInitialization`; factory-level even earlier in `invokeAwareMethods`). At that moment the **context is not fully refreshed** — other beans may not yet exist. **Best practice:** in the Aware setter, only store the reference; perform actual work (lookups, wiring, starting threads) in `@PostConstruct` or `afterPropertiesSet`, or better, in an `ApplicationListener<ContextRefreshedEvent>` / `SmartInitializingSingleton.afterSingletonsInstantiated()` when you need the whole context ready. ## 2. Prototype beans For a **prototype-scoped** bean (a new instance per request), the Aware callback runs **on every instantiation**. Also, Spring does **not manage the full lifecycle of prototypes** — it creates and configures them (so Aware and init callbacks do run) but does **not** call destruction callbacks. If a prototype uses `BeanFactoryAware` to grab resources, you own their cleanup. Repeatedly creating prototypes that each capture the context is a common leak/perf source. ## 3. Silent no-op in a bare BeanFactory As covered elsewhere: `ApplicationContextAware`, `EnvironmentAware`, `ResourceLoaderAware`, `MessageSourceAware`, `ApplicationEventPublisherAware` are injected by `ApplicationContextAwareProcessor`, which **only an ApplicationContext registers**. Wire beans into a plain `DefaultListableBeanFactory` and these callbacks **never fire, with no error** — the field stays null. `BeanNameAware`/`BeanFactoryAware` still work. This bites people writing lightweight tests or embedding a factory. ## 4. Reentrancy / touching half-built beans Calling `applicationContext.getBean(...)` or `beanFactory.getBean(...)` **inside** the Aware setter is dangerous: you may pull on a bean that is itself mid-construction, causing `BeanCurrentlyInCreationException`, or force **eager instantiation** that reorders startup and breaks otherwise-lazy graphs. Defer lookups until after refresh. If a lookup during init is unavoidable, prefer `ObjectProvider` which is lazy. ## 5. AOP proxy timing — the subtle one AOP (transactions, `@Async`, security, custom advice) is applied by BPPs in `postProcessAfterInitialization`, which runs **after** the Aware setters and after `@PostConstruct`. So the object executing your Aware setter and init methods is the **raw target**, not yet wrapped in a proxy. If you capture `this` in the Aware callback (e.g., to register yourself in a listener/registry), you have captured the **unproxied target** — later self-invocations through that reference **bypass the proxy's advice** (no transaction, no async). To register the advised bean, do it later (after full init) or look yourself up by name from the context. ## 6. Environment/property caveats `EnvironmentAware` gives the live `Environment`; property sources are generally in place early, but property-source post-processing (e.g., some `EnvironmentPostProcessor`/Boot config) happens during context prep, so reading properties in an Aware setter is usually safe — but never assume beans that contribute property sources are ready. ## 7. Order among multiple Aware interfaces If one bean implements several, the factory-level ones (`BeanNameAware`, `BeanClassLoaderAware`, `BeanFactoryAware`) come first, then the context-level ones in the fixed `ApplicationContextAwareProcessor` order (Environment → EmbeddedValueResolver → ResourceLoader → ApplicationEventPublisher → MessageSource → ApplicationStartup → ApplicationContext). Don't build logic that assumes a different interleaving. ## Term definitions - **`SmartInitializingSingleton`**: callback (`afterSingletonsInstantiated`) invoked once **all** non-lazy singletons are created — the right place for work needing a fully-built context. - **`ContextRefreshedEvent`**: application event published when the context is fully initialized/refreshed. - **`BeanCurrentlyInCreationException`**: thrown when a bean is requested while it is already partway through creation (a cycle/reentrancy). - **Advised/proxy bean**: the AOP wrapper Spring substitutes for the raw target so cross-cutting advice runs; created after init callbacks.
- Where should you put logic that needs the whole context ready, rather than in an Aware setter?In SmartInitializingSingleton.afterSingletonsInstantiated() (all non-lazy singletons built) or an ApplicationListener<ContextRefreshedEvent>. The Aware setter should only capture the reference.
- Why can a 'this' reference captured in an Aware callback bypass AOP advice on later self-calls?AOP proxies are created by a BeanPostProcessor in postProcessAfterInitialization, which runs after Aware setters. So 'this' during the setter is the raw target, not the proxy; self-invocations through it skip transactional/async/security advice.
- What happens to Aware and destruction callbacks for a prototype bean?Aware and init callbacks run on every prototype instantiation, but Spring does not track prototypes for destruction — no destroy callbacks fire, so the caller owns cleanup of anything the bean acquired.