Spring Bean Lifecycle
In this article we are going to discuss about spring bean lifecycle. it is very interesting topic, every spring developer must know about it. it is basic foundation of spring and help you to understand how dependency injection, bean management inside spring working under the hood.
First of all, we knows that in spring, beans are defined with a scope. scope helps to provide information how spring application container handles its creation, management and destruction. We have different types of scopes like singleton, prototype, request, session. here in this article we will mainly focus on singleton and prototype but you are feel free to explore other as well.
Lets start with Singleton, a bean with singleton scope means that bean will create only once per spring application container. so wherever our application use it, it only referring to that same instance. Even you can try to compare two different instance of singleton bean using == operator it will give you true.
Lets begin to understand singleton bean life cylce,
- When we start our spring application, first line of code that executes from our application is class annotated with
@SpringBootApplicationwhere we have lineSpringApplication.run(MyCustom.class, args);.
- This will create Application Context (Spring Container), Container inside will have BeanFactory and BeanDefinitionRegistory.
-
Bean Definition : Spring scans configuration:
@ComponentScan,@Configurationand XML (old).-
For each bean, Spring creates a BeanDefinition regardless of scope containing:
- Bean class
- Scope (singleton/prototype)
- Constructor info
- Dependency metadata
- Init & destroy methods
-
For each bean, Spring creates a BeanDefinition regardless of scope containing:
- After bean definition, bean instantiation and dependency injection happens, these are two separate process but dependency injection may trigger nested bean instantiation when resolving dependencies.
-
Bean instantiation, during bean instantiation bean instance created using constructor through reflection, so far it just create bean instance but dependency not injected yet.
BeanA beanA = new BeanA(); beanA.dependencyB = null; beanA.dependencyC = null;
-
Dependency Injection, during dependency injection, spring first resolves dependencies by type. If multiple beans of the same type exist, it filters the beans using
@Qualifier, then by matching bean name, and then by@Primary. If ambiguity still remains, an exception is thrownNoUniqueBeanDefinitionException. Once resolution is successful then dependency injected, through constructor , setter or field injection.beanA.dependencyB = new BeanB(); beanA.dependencyC = new BeanC();
- lets understand Dependency Injection in detail, because it will help to understand how spring detect circular dependency in case of constructor injection.
- Spring checks whatever beans requested pick those bean only. singleton bean get requested by spring it self during the start of application.
-
Now Spring instantiate requested bean,
- Check each dependency of that requested bean, resolve it
-
if the dependency is not yet instantiated , it instantiate that
-
now it pick the instantiated bean and check and resolve its dependency
- instantiate its dependency
- … this goes like in recursive way and cover instantiation and dependency injection of all dependencies, from top to bottom
- instantiated dependency injected to the caller bean
-
now it pick the instantiated bean and check and resolve its dependency
- instantiated dependency injected to the caller bean
- now the bean is wired with all its dependency.
Below is the diagram that shows this process.
Now lets understand with below diagram, how circular dependencies get detected using constructor injection.
-
After dependency injection Aware interfaces like
BeanNameAware,BeanFactoryAware,ApplicationContextAwareinterfaces called. These interfaces helps bean to access application container infrastructure related objects like name of bean in container,BeanFactoryobject,ApplicationContext, etc. there are various Aware interfaces present but here we are focusing on below 3.-
BeanNameAwareinterface provide information what will be bean name or id in application container, so you can log it for debugging purpose. this interface provide below method.setBeanName(String name)
-
BeanFactoryAwareinterface provide BeanFactory instance, using that we can access instance of Bean but it is not recommend to use, because when we use getBean() method of BeanFactory to get an instance of bean we are introducing tight coupling, in result will break Inversion of control principle (remember bean creation and management is spring responsibility not ours).💡getBean()returns the bean instance — but if the bean is not created yet, it will first create (instantiate + inject + initialize) it, then return it.this interface provides below method.
setBeanFactory(BeanFactory beanFactory)
-
ApplicationContextAwareinterface provide ApplicationContext instance, we can use it to access all beans inside container, resources, environment and other features of container. this is very powerful. this interface provides below method.setApplicationContext(ApplicationContext ctx)
- Important point here is that as a best practice we should try to avoid these interfaces, because using these we are breaking inversion of control principle.
-
-
After Aware interfaces,
BeanPostProcessorinterfacepostProcessBeforeIntialization()method calls, here you can write your custom logic to modify/replace bean instance before its init method runs. This bean lifecycle hook method helps to perform validation before initialization of bean. so, at last whatever get returns from this method will use by Spring, if you don’t modify it spring use original bean , if modified Spring will use modified bean.@Override public Object postProcessBeforeInitialization(Object bean, String name) { if (bean instanceof MyService) { // modify bean } return bean; }
-
Now, initialization will happen, which mean bean is wired with dependencies and all required information and it will run its startup logic/init method if defined. it’s not mandatory step, it happens if init method define via below config else it will get skip. below methods mentioned in order in which they execute.
- Method annotated with
@PostConstruct. It is commonly used to perform initialization logic such as validating dependencies, initializing resources, or warming caches in a DAO or service bean.
InitializingBeaninterfaceafterPropertiesSet()method (if bean implements InitializingBean interface)
- init method will get execute (if defined via
@Bean(initMethod=...)).
💡Here one point to note as a best practice
@PostConstruct or @Bean(initMethod=...)is recommended over InitializingBean,because if a bean using IntializingBean afterPropertiesSet() then that bean need to implement that interface, here bean represent a class which will have business logic, so that business logic will make tight coupling with Spring API (IntializingBean).
class MyService implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { // initialization logic here } }on the other hand,
@PostConstructis standard annotation provided by Jakarta not Spring, still keeps lifecycle management in business class but it is not Spring specific.@Bean(initMethod)keeps lifecycle management in the configuration layer instead of having it in the business class. hence, preserving separation of concerns.class MyService { private Cache cache; @PostConstruct public void postConstruct() { System.out.println("1 - PostConstruct"); cache.loadInitialData(); } public void init() { System.out.println("Custom initialization logic"); } } @Configuration class Config { @Bean(initMethod = "init") public MyService myService() { return new MyService(); } } - Method annotated with
-
After initialization,
BeanPostProcessorinterfacepostProcessAfteIntialization()called, this is very importation, because here AOP creates proxy of bean if it is require.here Spring AOP, @Transactional, @Async, @Cacheable are applied. its like Spring replace original bean with proxy and return it instead of actual object.
- After
postProcessAfterInitialization()completes, the final returned bean instance (which may be a proxy) is stored in the singleton cache of theApplicationContext. From that point onward, this instance is used wherever the bean is injected or requested.
-
Destruction phase, When the ApplicationContext is closed, ApplicationContext begins destroying singleton bean.
- if the bean has method annotated with
@PreDestoryget call, where you can write custom logic to close database connection, release resources or anything that need to handle before destroying bean.
- if bean implements
DisposableBeaninterface thendestroy()method also get call.
- if bean defines any destroy method using
@Bean(destroyMethod = "cleanup")then this is called.
- Finally, the bean is removed from the container and the
ApplicationContextshuts down.
- if the bean has method annotated with
This is brief information how bean lifecycle works, few points to remember.
- This is only for singleton scope bean.
- Because bean instantiation and initialization happens while application context creation, that is why bean with singleton scope is eagerly initialized by default. just an exception, If you mark a bean as
@Lazy, it will not be eagerly initialized.
ApplicationContextmanages the creation, caching, and destruction of singleton beans. When the ApplicationContext shuts down properly, methods annotated with @PreDestroy, DisposableBean.destroy(), and custom destroy methods are invoked.
Now lets discuss prototype scope bean, these are the bean created every time the container asked for it. so it means that if you are comparing 2 prototype scope bean of same type using ==, it will give false.
prototype bean follows a different lifecycle in comparison with singleton bean, although few steps looks similar to singleton bean lifecycle.
- First Spring application run and it creates
ApplicationContext(Spring Container) .
- Now in
ApplicationContext, bean definition gets created regardless of scope, which which have all the information require to create bean. similar to singleton bean.
- So far these 2 steps are same as singleton bean.
- Now if bean requested then only prototype bean get instantiated, here it is different from singleton. because singleton bean instantiation happens automatically. prototype bean’s instantiation process is same as singleton bean.
- After bean instantiation dependency injection happens. it is again same as singleton.
- After this Aware interfaces get called. same as singleton.
- After this,
BeanPostProcessor.postProcessBeforeIntialization()method calls, similar to singleton.
- Now, initialization will happen, which is again same as singleton.
- After this,
BeanPostProcessor.postProcessAfterIntialization()called, same as singleton.
- Now the instance created return to caller or the one who requested it.
- Prototype beans are not stored in the singleton cache. The container does not keep a managed reference after returning it. It does not track them for destruction. it is important difference between prototype and singleton.
- As it not get store in container and creates always on request that is why two prototype bean of same types are different.
- Prototype beans are not stored in the container’s singleton cache, and the container does not manage their full lifecycle beyond initialization. Therefore, destruction callbacks such as
@PreDestroy,DisposableBean.destroy(), or custom destroy methods defined in@Bean(destroyMethod=...)are not automatically invoked for prototype beans. If cleanup is required, we must manage the bean’s lifecycle manually.
- Also, one more point to note is that Prototype beans are created on demand, so they behave lazily by design.
Hope you like it, in case of any query or suggestion or anything you want to highlight, please mention in comments.




Comments
Post a Comment