Bean Lifecycle in Spring Framework: Complete Deep Dive
The Bean Lifecycle is arguably the most important architectural topic in Spring Core. It explains precisely how a bean is created, initialized, utilized, and eventually destroyed inside the Inversion of Control (IOC) Container.
Most developers can recite a basic summary:
1. What is the Bean Lifecycle?
Standard Definition
The Bean Lifecycle is the complete chronological journey of a Spring Bean from its metadata parsing to its destruction inside the Spring IOC Container.
High-Level Initialization Flow
2. The Complete Initialization Sequence (Step-by-Step)
Phase 1: Bean Definition Creation
When Spring performs a @ComponentScan, it does NOT create the object immediately in memory. First, it generates an internal metadata blueprint.
If Spring finds @Service public class EmployeeService {}, it creates a BeanDefinition containing:
- Bean Name:
employeeService - Class:
EmployeeService - Scope:
singleton - Lazy:
false - Dependencies: (Extracted from constructor)
Internal Flow: @Component → ClassPathBeanDefinitionScanner → BeanDefinition → BeanDefinitionRegistry.
Phase 2: Bean Instantiation
Spring now generates the actual raw Java object in memory using the Reflection API.
This is the very first lifecycle callback—the Constructor Execution.
Phase 3: Dependency Injection
Spring analyzes the newly instantiated object to see what it requires. If EmployeeService requires an EmployeeRepository, the container searches its registry, initializes the repository (if necessary), and injects it.
Internal Engine: AutowiredAnnotationBeanPostProcessor handles the resolution and injection of @Autowired and constructor dependencies.
Phase 4: Aware Interfaces (Crucial for Senior Interviews)
After dependencies are injected, Spring checks if the bean implements any special "Aware" interfaces. These interfaces allow the bean to receive underlying framework infrastructure objects.
BeanNameAware: ExecutessetBeanName()so the bean knows its own registered name.BeanFactoryAware: ExecutessetBeanFactory()to provide direct access to the factory.ApplicationContextAware: ExecutessetApplicationContext()to provide access to the full container context.
Phase 5: BeanPostProcessor (Before Initialization)
Before any custom initialization logic runs, Spring executes a crucial interceptor interface across all beans: the BeanPostProcessor.
Specifically, it triggers the postProcessBeforeInitialization() method. A core internal processor here is the CommonAnnotationBeanPostProcessor, which is physically responsible for invoking the @PostConstruct annotation.
Phase 6: Initialization Callbacks
Once the framework setup is complete, Spring executes user-defined initialization logic in a strict order:
@PostConstruct: Executed first. Used for cache loading, database warming, or API client setup.InitializingBean.afterPropertiesSet(): Executed second if the bean implements theInitializingBeaninterface.- Custom Init Method: Executed last if you specified
@Bean(initMethod = "start")in a configuration class.
Phase 7: BeanPostProcessor (After Initialization) & Proxy Creation
Spring triggers postProcessAfterInitialization(). This is a massively important phase.
If your bean requires transactional boundaries (@Transactional), asynchronous execution (@Async), caching (@Cacheable), or Spring Security, this is exactly where Spring generates the AOP Proxy wrapper around your raw bean.
Phase 8: Bean Ready & Singleton Cache
The fully initialized (and potentially proxied) bean is finally ready to serve traffic. It is moved into the framework's primary singleton cache.
Internal Cache: Stored inside the singletonObjects map managed by the DefaultSingletonBeanRegistry.
3. The Singleton Creation Cache Pattern
By default, Spring beans are @Scope("singleton"). This means only ONE object exists in the entire JVM memory space for that definition.
When multiple controllers request the EmployeeService via @Autowired, they are all handed the exact same memory reference (System.identityHashCode(service) proves this).
The Three-Level Cache (Advanced Concept)
To resolve Circular Dependencies (e.g., Bean A needs Bean B, but Bean B needs Bean A), Spring maintains a highly sophisticated three-tiered cache system:
- Level 1:
singletonObjects→ Fully initialized, ready-to-use beans. - Level 2:
earlySingletonObjects→ Partially initialized beans (raw objects waiting on dependencies). - Level 3:
singletonFactories→ Object factories capable of generating early references (used to build AOP proxies early if needed).
Resolution Flow: singletonFactories → earlySingletonObjects → singletonObjects.
4. The Destruction Lifecycle
When the application begins shutting down (e.g., context.close() or a JVM termination signal), Spring orchestrates a graceful shutdown of all singleton beans to prevent data corruption or orphaned connections.
Destruction Callbacks
Just like initialization, destruction logic runs in a strict sequential order:
@PreDestroy: Executed first. Primarily used to close open files, release network resources, flush database connections, or stop background threads.DisposableBean.destroy(): Executed second if the bean implements theDisposableBeaninterface.- Custom Destroy Method: Executed last if specified via
@Bean(destroyMethod = "stop").
5. Master Interview Checklist
The Complete Ordered Sequence (Memorize This)
Top Interview Q&A
Q: Explain the Bean Lifecycle in summary.
A: It begins with BeanDefinition metadata generation, proceeds to raw instantiation via reflection, and handles dependency injection. It then triggers aware interfaces and initialization callbacks (@PostConstruct, afterPropertiesSet). Finally, post-processors wrap the bean in AOP proxies if required, storing it in the singleton cache for runtime use until the container shuts down and triggers destruction callbacks (@PreDestroy).
Q: Where exactly are Singleton Beans physically stored?
A: They are cached inside a map called singletonObjects, managed internally by the DefaultSingletonBeanRegistry class.
Q: Why is @PostConstruct necessary if we have a Constructor?
A: The constructor executes *before* dependencies are injected. If your initialization logic (like fetching initial data from a database) requires an injected repository, attempting to call it in the constructor will result in a NullPointerException. @PostConstruct guarantees that all dependencies have been successfully wired before executing the logic.
Q: When exactly are AOP Proxies created?
A: AOP proxies (which enable annotations like @Transactional, @Async, and @Cacheable) are typically created during the final initialization phase inside BeanPostProcessor.postProcessAfterInitialization().
Comments
Post a Comment