1. Diving into the lifecycle of Spring Beans
Main stages of a Spring Bean lifecycle:
- Creation (Initialization): The IoC container creates the object (bean) and injects all dependencies.
- Initialization (Post Initialization): additional operations are performed, like setup and starting any required processes.
- Destruction (Destruction): before the container shuts down the bean can finish its tasks, free resources, and perform cleanup.
Let's see how Spring manages these stages and what tools it gives you to configure them.
2. How does the IoC container manage the lifecycle?
The Spring IoC container is responsible for:
- Creating objects from configuration or annotations
- Wiring dependencies into the bean
- Running initialization methods
- Calling destruction methods when a bean is no longer needed
In a real app it goes like this:
- Spring creates the Bean
- Spring injects dependencies via DI (@Autowired, @Bean, @Component)
- Spring runs methods annotated with
@PostConstructafter the object is created - When the app shuts down, Spring calls methods annotated with
@PreDestroyto properly shut down the object
3. Using annotations to work with the lifecycle
Spring provides handy annotations for managing a bean's lifecycle: @PostConstruct and @PreDestroy. Now let's dig in.
@PostConstruct: initialization method
This annotation marks a method that's called after the object is created and dependencies are injected. It's perfect for extra setup before the object is used.
Example:
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class CoffeeMaker {
@PostConstruct
public void heatUp() {
System.out.println("Coffee maker is heating up...");
}
}
When Spring creates the CoffeeMaker bean, it will automatically call heatUp after all dependencies are set up.
@PreDestroy: destruction method
This annotation marks a method that will be called before the object is destroyed. It's useful for releasing resources or doing final tasks.
Example:
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
@Component
public class CoffeeMaker {
@PreDestroy
public void shutDown() {
System.out.println("Coffee maker is shutting down...");
}
}
When Spring shuts down the app, the shutDown method will be called automatically.
4. Configuring custom lifecycle methods
Sometimes the default options aren't enough and you need more flexibility. Spring gives you two ways to add your own init and destroy methods.
Defining methods in Java configuration
When using @Bean you can specify custom methods for initialization and destruction.
Example:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public CoffeeMaker coffeeMaker() {
return new CoffeeMaker();
}
}
class CoffeeMaker {
public void init() {
System.out.println("Coffee maker is ready to go!");
}
public void destroy() {
System.out.println("Coffee maker is shutting down...");
}
}
On bean initialization Spring will call init, and on shutdown it will call destroy.
Implementing InitializingBean and DisposableBean
If you prefer interfaces, Spring provides two options:
InitializingBean— for initialization methods.DisposableBean— for destroy methods.
Example:
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class CoffeeMaker implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() {
System.out.println("Coffee maker is ready to go! (via InitializingBean)");
}
@Override
public void destroy() {
System.out.println("Coffee maker is shutting down... (via DisposableBean)");
}
}
Programmers often joke: "If you have an interface for everything, you're probably writing Java!" Spring was the same, but with the arrival of annotations @PostConstruct and @PreDestroy, devs started using interfaces less often.
5. Spring Bean lifecycle in practice
Now let's look at a full example using annotations. We'll create a CoffeeMaker class with @Component and set up its lifecycle.
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class CoffeeMaker {
@PostConstruct
public void heatUp() {
System.out.println("Coffee maker is heating up...");
}
@PreDestroy
public void shutDown() {
System.out.println("Coffee maker is shutting down...");
}
public void brewCoffee() {
System.out.println("Brewing coffee!");
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
coffeeMaker.brewCoffee();
context.close();
}
}
@Configuration
@ComponentScan("com.example")
class AppConfig {}
Console output:
Coffee maker is heating up...
Brewing coffee!
Coffee maker is shutting down...
Note that heatUp runs automatically after the object is created, and shutDown runs before shutdown.
6. Practical tips
- Use
@PostConstructand@PreDestroywhen you can. These annotations are simple, clear, and made specifically for lifecycle work. - Avoid complex logic in init or destroy methods. Methods annotated with these should be used for setup, checks, or registering required components.
- Watch your resources. If your bean uses external resources (for example, a database or network connections), be sure to release them in the destroy method.
- Regularly test the lifecycle. This is especially important in apps with complex configuration.
Once you understand the Spring Beans lifecycle and how to control creation, initialization, and shutdown, you'll be able to much better manage your application's behavior. This knowledge is useful at every stage of development, from small utilities to designing scalable microservices.
GO TO FULL VERSION