Spring Boot’s bean lifecycle is a lot more than just "create it, use it, throw it away."
Here’s a simple Spring Boot application that demonstrates the bean lifecycle in action. We’ll create a MyComponent class and define a few methods to hook into its lifecycle.
package com.example.demo;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.UUID;
@Component
public class MyComponent implements InitializingBean, DisposableBean {
private final String id;
public MyComponent() {
this.id = UUID.randomUUID().toString();
System.out.println("1. Constructor: Creating MyComponent with ID: " + this.id);
}
@PostConstruct
public void postConstruct() {
System.out.println("2. @PostConstruct: Initializing MyComponent with ID: " + this.id);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("3. InitializingBean.afterPropertiesSet: Post-property injection initialization for MyComponent with ID: " + this.id);
}
public void customInitMethod() {
System.out.println("4. Custom Init Method: Performing custom initialization for MyComponent with ID: " + this.id);
}
public void doSomething() {
System.out.println("5. Usage: MyComponent with ID: " + this.id + " is doing something.");
}
@PreDestroy
public void preDestroy() {
System.out.println("6. @PreDestroy: Preparing to destroy MyComponent with ID: " + this.id);
}
@Override
public void destroy() throws Exception {
System.out.println("7. DisposableBean.destroy: Performing cleanup for MyComponent with ID: " + this.id);
}
public void customDestroyMethod() {
System.out.println("8. Custom Destroy Method: Performing custom cleanup for MyComponent with ID: " + this.id);
}
}
And here’s how you’d run it and trigger the lifecycle methods, often by having another component inject it or by explicitly calling a method on it:
package com.example.demo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
CommandLineRunner runner(MyComponent myComponent) {
return args -> {
System.out.println("\n--- Application Running ---\n");
myComponent.doSomething();
System.out.println("\n--- Application Shutting Down ---\n");
};
}
}
When you run this, you’ll see output like this (the UUID will differ):
1. Constructor: Creating MyComponent with ID: a1b2c3d4-e5f6-7890-1234-567890abcdef
2. @PostConstruct: Initializing MyComponent with ID: a1b2c3d4-e5f6-7890-1234-567890abcdef
3. InitializingBean.afterPropertiesSet: Post-property injection initialization for MyComponent with ID: a1b2c3d4-e5f6-7890-1234-567890abcdef
4. Custom Init Method: Performing custom initialization for MyComponent with ID: a1b2c3d4-e5f6-7890-1234-567890abcdef
--- Application Running ---
5. Usage: MyComponent with ID: a1b2c3d4-e5f6-7890-1234-567890abcdef is doing something.
--- Application Shutting Down ---
6. @PreDestroy: Preparing to destroy MyComponent with ID: a1b2c3d4-e5f6-7890-1234-567890abcdef
7. DisposableBean.destroy: Performing cleanup for MyComponent with ID: a1b2c3d4-e5f6-7890-1234-567890abcdef
8. Custom Destroy Method: Performing custom cleanup for MyComponent with ID: a1b2c3d4-e5f6-7890-1234-567890abcdef
The core problem Spring solves here is managing the entire lifecycle of your application’s components (beans) in a predictable and configurable way. Instead of manually creating, configuring, and cleaning up objects, Spring handles it. This allows you to focus on the business logic. It’s essentially a sophisticated object factory and manager.
The lifecycle isn’t just a sequence; it’s a series of hooks you can attach to. Here’s the order in which Spring processes a bean:
- Instantiation (Constructor): This is where the bean object is created. You can’t do much here except basic object construction. Spring calls the default constructor or a factory method.
- Dependency Injection: Spring injects any dependencies the bean requires. This happens after the constructor but before any initialization callbacks.
@PostConstruct: This is a standard Java annotation. It’s called after dependency injection is complete for the bean. This is a great place for any setup that relies on injected dependencies.InitializingBean.afterPropertiesSet(): This is a Spring-specific interface. If your bean implementsInitializingBean, itsafterPropertiesSet()method is called after@PostConstruct(if present) and after all properties have been set. It’s another common place for initialization logic.- Custom Init Method: You can define your own methods (like
customInitMethod) and configure Spring to call them as initialization callbacks using@Bean(initMethod = "customInitMethod")or<bean init-method="customInitMethod">in XML. These are called afterafterPropertiesSet(). - Bean is Ready: The bean is now fully initialized and ready for use. Spring injects it into other beans or makes it available for you to use.
@PreDestroy: This is another standard Java annotation. It’s called before the bean is removed from the Spring application context. This is for cleanup tasks that don’t necessarily need to implement a specific Spring interface.DisposableBean.destroy(): This is the Spring-specific interface for destruction. If your bean implementsDisposableBean, itsdestroy()method is called after@PreDestroy(if present). It’s for Spring-managed cleanup.- Custom Destroy Method: Similar to init methods, you can define custom destroy methods using
@Bean(destroyMethod = "customDestroyMethod")or<bean destroy-method="customDestroyMethod">in XML. These are called afterDisposableBean.destroy(). - Bean is Destroyed: The bean is fully cleaned up and garbage collected.
The most surprising thing about this lifecycle is that the order of initialization callbacks is fixed and predictable: Constructor -> @PostConstruct -> InitializingBean.afterPropertiesSet() -> Custom Init Method. This deterministic sequence is crucial for ensuring that a bean is fully ready and its dependencies are available before its own initialization logic runs.
The one thing most people don’t know is that Spring’s default behavior for @Bean methods is to infer the destroyMethod name. For example, if you have a close() or shutdown() method on your bean, Spring will often call it automatically when the application context closes, even if you don’t explicitly specify destroyMethod. This can be a convenient shortcut but also a source of surprise if you don’t realize Spring is doing it for you.
The next concept you’ll run into is how Spring manages bean scopes, which dictates when these lifecycle callbacks are invoked for beans other than singletons.