Bean Lifecycle & Scope
IntermediateSpring manages beans through a well-defined lifecycle; scopes (singleton, prototype, request, session) determine how many instances are created and when they are destroyed.
Overview
Every Spring bean passes through a well-defined lifecycle managed by the ApplicationContext. Understanding this lifecycle is essential for writing correct initialisation (warm up a connection pool, load a cache) and cleanup logic (flush buffers, close resources). The lifecycle phases are: instantiation → dependency injection → BeanNameAware / BeanFactoryAware callbacks → @PostConstruct / InitializingBean → bean is in use → @PreDestroy / DisposableBean → destroyed. Alongside lifecycle, scope controls how many instances Spring creates: singleton (one per context, the default), prototype (new instance on each request), and web-scoped variants (request, session, application).
Bean Lifecycle Phases
The full lifecycle in order: 1. Instantiation — Spring calls the constructor. 2. Dependency injection — @Autowired fields and setters are populated. 3. *Aware callbacks — Spring injects BeanName, BeanFactory, ApplicationContext if the bean implements the relevant Aware interface. 4. BeanPostProcessor.postProcessBeforeInitialization — e.g., @PostConstruct is processed here. 5. InitializingBean.afterPropertiesSet() / @PostConstruct — custom init logic runs. 6. BeanPostProcessor.postProcessAfterInitialization — AOP proxies are created here. 7. Bean is in use by the application. 8. @PreDestroy / DisposableBean.destroy() — called on context shutdown.
@Component
public class CacheService implements InitializingBean, DisposableBean {
private Map<String, String> cache;
// Phase 4+5 — runs after all dependencies are injected
@PostConstruct
public void init() {
cache = new ConcurrentHashMap<>();
System.out.println("CacheService initialised");
}
// Alternative to @PostConstruct — implements InitializingBean
@Override
public void afterPropertiesSet() {
// Same timing as @PostConstruct — pick one style
}
// Phase 8 — runs on context shutdown (Ctrl+C or System.exit)
@PreDestroy
public void cleanup() {
cache.clear();
System.out.println("CacheService destroyed");
}
@Override
public void destroy() {
// Alternative to @PreDestroy — implements DisposableBean
}
}Bean Scopes
Scope determines how many instances Spring creates and when they are returned:
**singleton** (default) — one shared instance per ApplicationContext. All injections receive the same object. Thread-safe design is your responsibility.
**prototype** — a new instance is created each time the bean is requested (each @Autowired injection or ApplicationContext.getBean() call). Spring does NOT call @PreDestroy on prototype beans.
**request** — one instance per HTTP request (Spring MVC).
**session** — one instance per HTTP session.
**application** — one instance per ServletContext (shared across sessions).
Inject a narrower-scoped bean (e.g., request) into a wider-scoped bean (e.g., singleton) using a scoped proxy.
// Singleton (default) — omitting @Scope means singleton
@Service // one instance for the lifetime of the app
public class OrderService { }
// Prototype — new instance every time
@Component
@Scope("prototype")
public class CsvExporter { }
// Request scope — one per HTTP request
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private String traceId = UUID.randomUUID().toString();
}
// Inject request-scoped bean into singleton safely via proxy
@Service
public class AuditService {
@Autowired
private RequestContext requestContext; // Spring injects a proxy, not the real bean
}BeanPostProcessor — Hooking Into Every Bean
BeanPostProcessor lets you intercept and transform every bean in the context at initialisation time. Spring uses this internally for @PostConstruct processing, @Autowired injection, and AOP proxy creation. You can write your own for logging, instrumentation, or custom annotation processing.
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// called BEFORE @PostConstruct
System.out.println("Before init: " + beanName);
return bean; // always return bean (or a replacement)
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// called AFTER @PostConstruct — AOP proxies are created here
System.out.println("After init: " + beanName);
return bean;
}
}Key Points to Remember
- 1@PostConstruct runs after all dependencies are injected — safe to use them; @PreDestroy runs on context shutdown — ideal for resource cleanup.
- 2Singleton scope means one shared instance per context — state in singleton beans must be thread-safe.
- 3Prototype beans are NOT destroyed by Spring — the caller is responsible for cleanup if needed.
- 4Never inject a prototype bean into a singleton with a plain @Autowired — the prototype is only created once. Use ObjectFactory<T> or ApplicationContext.getBean() for fresh instances.
- 5BeanPostProcessor intercepts every bean and is how Spring implements @PostConstruct, @Autowired, and AOP proxies.
- 6Use @Scope proxyMode=TARGET_CLASS to safely inject request/session-scoped beans into singleton beans.
Interview Questions
Sign in to ask AriaWhat is the difference between @PostConstruct and InitializingBean.afterPropertiesSet()?
If a singleton bean holds a prototype dependency, does each caller get a fresh prototype instance? Why or why not?
What is the role of BeanPostProcessor and how does Spring use it internally?
When does @PreDestroy NOT get called?
How do you inject a request-scoped bean into a singleton-scoped bean safely?
Ask Aria about Bean Lifecycle & Scope
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.