In this article, you will learn the Spring bean lifecycle and different ways to run bean initialization callbacks and bean destruction callbacks. Bean lifecycle simply means you want to execute callbacks before the spring bean is available to use and similarly execute callbacks before the bean is destroyed.
There are several ways to configure the Spring bean lifecycle callbacks as listed below.
- The JSR-250 specification using
@PostConstruct
and@PreDestroy
annotations. This is the recommended way. - Using the lifecycle callbacks in
<bean/>
or@bean
declarations. - Using the default initialization and destroy methods.
- Startup and Shutdown callbacks from the
Lifecycle
interface.
It is recommended to use JSR-250
@PostConstruct
and@PreDestroy
annotations as a best practice for executing lifecycle callbacks in a Spring application.
If you are using JDK 9 or higher, please make sure you add the javax.annotation-api
dependency for JSR-250 to work.
<!-- Use the recent version shown below -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
For later version, 1.3.3 onwards use jakarta.annotation-api
instead of javax.annotation-api
.
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
</dependency>
Maven repoSpring Bean creation and destruction phases
Before exploring the ways to invoke lifecycle callbacks, it is important to understand the different phases a Spring Bean goes through before it is ready to use and before it is destroyed.
The below diagram shows the different stages spring bean goes through before it is ready to use. If you carefully observe the diagram below, the last 3 phases are mentioned in the above discussion about several ways to specify bean lifecycle callbacks.
Similarly, the image below shows the different stages Spring bean goes through before the IoC shuts it down. I have listed all the 3 phases in the above list.
1. Use @PostConstruct and @PreDestroy annotations – (JSR-250)
The JSR-250 specification provides 2 annotations, @PostConstruct
and @PreDestroy
. If you want logic to be executed before the bean is ready to use by the programs, use @PostCostruct. Similarly, if you want the callback to be executed before the bean gets destroyed, use @PreDestroy
. These annotations are used on the method level. The CommonAnnotationBeanPostProcessor
in Spring is responsible for the recognition of these lifecycle annotations.
The example below demonstrates the working of these annotations.
package bean.jsr.callbacks;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class JsrExampleBean {
@PostConstruct
public void initCacheData() {
System.out.println("Your custom initialization code goes here...");
}
@PreDestroy
public void clearCacheData() {
System.out.println("Your custom destroy code goes here...");
}
}
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("bean.jsr.callbacks")
public class JsrExampleConfig {
//Extra config goes
}
Javapackage bean.jsr.callbacks;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestJsrExample {
public static void main(String[] args) {
ConfigurableApplicationContext context
= new AnnotationConfigApplicationContext(JsrExampleConfig.class);
context.getBean(JsrExampleBean.class);
//This is important to shutdown gracefully
context.registerShutdownHook();
}
}
Output:
Your custom initialization code goes here... Your custom destroy code goes here...
Note the way ApplicationContext is shutdown gracefully in a non-web spring application by invoking ConfigurableApplicationContext.registerShutdownHook();
.
2. Using the Initialization and Destruction callbacks in bean config
Spring uses BeanPostProcessor
implementations to process callback interfaces and the appropriate methods.
You can configure them in init-method
and destroy-method
of bean configuration. Assign the custom initialization callback to the init-method
property of bean definitions and the custom destruction callback to destroy-method
.
The below code demonstrates how the custom initialization and destruction callbacks are configured. It represents the annotation-based java configurations.
package bean.lifecycle.callbacks;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("bean.lifecycle.callbacks")
public class BLCConfigClass {
@Bean(initMethod = "init", destroyMethod = "destroy")
public ExampleBean1 exampleBean1() {
return new ExampleBean1();
}
}
package bean.lifecycle.callbacks;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestInitDestroy {
public static void main(String[] args) {
ConfigurableApplicationContext context
= new AnnotationConfigApplicationContext(BLCConfigClass.class);
//This is important
context.registerShutdownHook();
}
}
package bean.lifecycle.callbacks;
public class ExampleBean1 {
public void init() {
System.out.println("Initialization method of " + this.getClass().getName());
}
public void destroy() {
System.out.println("Destroy method of " + this.getClass().getName());
}
}
Output:
Initialization method of bean.lifecycle.callbacks.ExampleBean1 Destroy method of bean.lifecycle.callbacks.ExampleBean1
If you are using XML based configuration, the callbacks can be easily configured as below.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="exampleBean" class="bean.lifecycle.callbacks.ExampleBean1" init-method="init" destroy-method="destroy"/>
</beans>
Implement the InitializingBean and DisposableBean interfaces
Instead of configuring the callbacks in bean definition, you can implement InitializingBean.afterPropertiesSet()
method for initialization and DisposableBean.destroy()
method for destruction callbacks in your bean. The below example represents this concept.
package bean.lifecycle.callbacks;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class ExampleBean2 implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Initialization method of " + this.getClass().getName());
}
@Override
public void destroy() throws Exception {
System.out.println("Destroy method of " + this.getClass().getName());
}
}
It is not recommended to implement
InitializingBean
andDisposableBean
as it unnecessarily creates tight coupling. Instead use the bean configuration or JSR-250 annotations as discussed before.
3. Default Initialization and Destroy methods
There can be a use case where instead of configuring the initialization and destruction callbacks individually, you want to configure them globally. This way you can just configure it once and apply it on all beans by specifying it with default-init-method
and default-destroy-method
.
Care must be taken in this approach to ensure that all the Java Classes use the same method names as the initialization & destruction callbacks. E.g. you can use init()
, initialize()
etc for initialization and dispose()
, clear()
or destroy()
etc for destruction callbacks. You can make all your classes implement a custom interface to ensure the method name remains the same.
I have represented a simple example below.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
default-init-method="init" default-destroy-method="destroy">
<context:component-scan base-package="bean.dfault.callbacLks"/>
</beans>
package bean.dfault.callbacks;
public interface LifecycleBean {
//initialization callback
public void init();
//Destruction callbacks
public void destroy();
}
package bean.dfault.callbacks;
import org.springframework.stereotype.Component;
@Component
public class MyServiceA implements LifecycleBean {
@Override
public void init() {
System.out.println("Initialization method of " + this.getClass().getName());
}
@Override
public void destroy() {
System.out.println("Destroy method of " + this.getClass().getName());
}
}
package bean.dfault.callbacks;
import org.springframework.stereotype.Component;
@Component
public class MyServiceB implements LifecycleBean {
@Override
public void init() {
System.out.println("Initialization method of " + this.getClass().getName());
}
@Override
public void destroy() {
System.out.println("Destroy method of " + this.getClass().getName());
}
}
package bean.dfault.callbacks;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestDefaultCallbacks {
public static void main(String[] args) {
ConfigurableApplicationContext context
= new ClassPathXmlApplicationContext("bean.config.xml");
//This is important
context.registerShutdownHook();
}
}
Output:
Initialization method of bean.dfault.callbacks.MyServiceA Initialization method of bean.dfault.callbacks.MyServiceB Destroy method of bean.dfault.callbacks.MyServiceB Destroy method of bean.dfault.callbacks.MyServiceA
What happens if all 3 mechanisms combined?
If you combine all the above mentioned 3 mechanisms into a single bean, the callbacks will be executed in the following order:
If the bean is getting created
- First, the method annotated with
@PostConstruct
is executed. - Then, the
afterPropertiesSet()
defined byInitializingBean
gets executed. - Third, a custom configured
init()
gets executed.
Similarly, if the bean is getting destroyed
- Method with
@PreDestroy
gets executed - Then the method
destroy()
from DisposableBean is executed. - Finally, a custom configured
destroy()
method runs.
4. Spring Lifecycle interface Startup and Shutdown callbacks
The Lifecycle interface of Spring framework provides start()
, stop()
and isRunning()
to invoke callbacks when the ApplicationContext
receives start and stop signals at runtime. This allows the programmer to control the processes running in the background.
The Lifecycle interface looks as below.
package org.springframework.context;
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
Here is an example that shows the working of Lifecycle
interface.
package bean.start.stop.callbacks;
import org.springframework.context.Lifecycle;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class LifecycleExampleBean implements Lifecycle {
private boolean status = false;
@Override
public void start() {
System.out.println("Inside the Lifecycle start method");
this.status = true;
}
@Override
public void stop() {
System.out.println("Inside the Lifecycle stop method");
this.status = false;
}
@Override
public boolean isRunning() {
return this.status;
}
}
package bean.start.stop.callbacks;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestLifecycleBean {
public static void main(String[] args) {
ConfigurableApplicationContext context
= new AnnotationConfigApplicationContext(LifecycleExampleBean.class);
context.start();
context.getBean(LifecycleExampleBean.class);
context.stop();
context.start();
//This is important - shutdown gracefully
context.registerShutdownHook();
}
}
Output:
Inside the Lifecycle start method Inside the Lifecycle stop method Inside the Lifecycle start method Inside the Lifecycle stop method
NOTE: Spring
Lifecycle
interface is a plain contract for explicit start and explicit stop notifications. It does not work with the auto-start at context refresh time.For fine-grained control over auto-startup of a specific bean (including startup phases), consider implementing SmartLifecycle instead.
The SmartLifecycle interface
When we have a use case where object with certain typ should start first then another type, in these cases SmartLifecycle
plays an important role. The getPhase()
method returns an integer value, objects with the lowest phase value start first. So the object that returns Integer.MIN_VALUE
as the phase value will be started first and will be stopped last.
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
public interface Phased {
int getPhase();
}
ApplicationContextAware, BeanNameAware, and Other Aware interfaces
We are touching an advanced part of the Spring Framework. This concept is kind of contradicts the Spring IoC and Dependency Injection concepts.
If you need an instance of `ApplicationContext
inside your bean for whatsoever reason, your bean needs to implement ApplicationContextAware
interface. This is a bit reverse of the dependency injection, but will not get into that discussion now.
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
Similarly, if you need to modify the bean name, you can implement BeanNameAware
interface in you bean. This will allow you to rename your bean before it is ready to use.
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
Other Aware Interfaces
Apart from ApplicationContextAware
and BeanNameAware
interfaces, spring offers a range of Aware
interfaces. Refer to the Official Spring Doc for more information on Aware interfaces.
Aware Interface | Method to Override | Explanation |
---|---|---|
ApplicationContextAware | void setApplicationContext (ApplicatinContext …) | Implement this interface to get access to ApplicationContext |
ApplicationEventPublisherAware | void setApplicationEventPublisher (ApplicationEventPublisher …) | Class implements this interface to publish custom ApplicationEvent . |
BeanClassLoaderAware | void setBeanClassLoader (ClassLoader …) | Custom callback to supply bean class loader. |
BeanFactoryAware | void setBeanFactory (BeanFactory …) | Custom callback to supply BeanFactory to the bean instance |
BeanNameAware | void setBeanName (String name) | Set the name of the bean. |
BootstrapContextAware | void setBootstrapContext (BootstrapContext …) | Set the BootstrapContext for the bean |
LoadTimeWeaverAware | void setLoadTimeWeaver (LoadTimeWeaver …) | Waver for processing class definition at load time. |
MessageSourceAware | void setMessageSource (MessageSource …) | Configure the startegy for resolving messages. |
NotificationPublisherAware | void setNotificationPublisher (NotificationPublisher …) | Spring JMX notification publisher. |
ResourceLoaderAware | void setResourceLoader (ResourceLoader …) | Configure loader for low-level access to resources. |
ServletConfigAware | void setServletConfig (ServletConfig …) | Modify the ServletConfig in which the bean runs. |
ServletContextAware | void setServletContext (ServletContext …) | Set the ServletContext in which bean runs. |
I hope you got a good understanding of Spring bean lifecycle callbacks, it is a good practice to just stick to JSR-250 annotations. Bean aware interfaces are handy when you need more control over Spring. Spring also provides @EventListener
to customize behavior, discussed in another article. You can also download the code examples from GitHub.