Here I systematically explore Spring's lifecycle methods by implementing them one by one in a structured way π
Step 1: Understanding registerBeanDefinition()
in Practice
We are starting with how Spring registers bean definitions before instantiation using BeanDefinitionRegistry
.
πΉ What Happens at This Stage?
- Spring loads and registers bean definitions from:
@ComponentScan
-
@Bean
methods inside@Configuration
- XML-based configuration (if used)
- At this point, no beans are instantiated yet! Spring is just storing the metadata about each bean.
πΉ Practical Example: Registering a Bean Manually
- We define a class
CustomBeanRegistrar
that implementsBeanDefinitionRegistryPostProcessor
. - Inside
postProcessBeanDefinitionRegistry()
, we register a bean definition manually. - We create a bean class (
MyDynamicBean
) that will be instantiated when accessed.
@Configuration
public class CustomBeanRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// Define a new bean manually
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(MyDynamicBean.class);
registry.registerBeanDefinition("myDynamicBean", beanDefinition);
System.out.println("Custom bean definition registered: MyDynamicBean");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// Not needed for this example
}
}
πΉ Key Takeaways
β
This method is used to dynamically register beans (e.g., based on conditions, external configs).
β
At this stage, beans are just "blueprints"βactual instances are not created yet.
β
Best Practice: Only use this when dynamic bean registration is needed (e.g., custom plugins).
Step 2: Understanding postProcessBeforeInstantiation()
Now that we've covered bean definition registration, the next lifecycle method is InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation()
.
πΉ What Happens at This Stage?
- This method runs before the actual instantiation of a bean.
- It allows you to:
- Modify or replace the bean instance before it is created.
- Prevent normal instantiation by returning a proxy or a custom instance.
πΉ Practical Example: Intercepting Bean Instantiation
We've implemented InstantiationAwareBeanPostProcessor
to log when this method is called.
@Component
class CustomInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("Before instantiating bean: " + beanName);
return null; // Returning null allows normal instantiation
}
}
π‘ Key Observations
β
This method executes before the constructor is called.
β
You can return a different instance instead of allowing the default instantiation.
β
Best Practice: Use it for AOP (Aspect-Oriented Programming) scenarios like creating proxies.
Step 3: Understanding setBeanName()
Now that we've covered postProcessBeforeInstantiation()
, the next lifecycle method is BeanNameAware#setBeanName()
.
πΉ What Happens at This Stage?
- After the bean is instantiated, Spring calls
setBeanName()
. - This allows the bean to know its own name in the application context.
- This happens before dependency injection.
πΉ Practical Example: Bean Recognizing Its Own Name
We've extended the existing application to include BeanNameAware
inside MyDynamicBean
:
class MyDynamicBean implements BeanNameAware {
private String beanName;
public MyDynamicBean() {
System.out.println("MyDynamicBean instantiated!");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("Bean name set: " + name);
}
}
π‘ Key Observations
β
The method is called immediately after instantiation.
β
The bean can store its own name for later use.
β
Best Practice: Use this when the bean needs to log or manipulate its own name.
Step 4: Understanding setBeanClassLoader()
Now that we've covered setBeanName()
, the next lifecycle method is BeanClassLoaderAware#setBeanClassLoader()
.
πΉ What Happens at This Stage?
- After setting the bean name, Spring calls
setBeanClassLoader(ClassLoader classLoader)
. - This method allows the bean to access the ClassLoader that loaded it.
- The ClassLoader is responsible for loading classes into memory.
πΉ Practical Example: Accessing Bean ClassLoader
We've extended our MyDynamicBean
class to implement BeanClassLoaderAware
:
class MyDynamicBean implements BeanNameAware, BeanClassLoaderAware {
private String beanName;
private ClassLoader classLoader;
public MyDynamicBean() {
System.out.println("MyDynamicBean instantiated!");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("Bean name set: " + name);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
System.out.println("Bean ClassLoader set: " + classLoader);
}
}
π‘ Key Observations
β
The setBeanClassLoader()
method is called after setBeanName()
.
β
The ClassLoader
allows the bean to dynamically load classes at runtime.
β
Best Practice: Use it when you need to load classes dynamically (e.g., plugins, external dependencies).
Step 5: Understanding setBeanFactory()
Now that we've covered setBeanClassLoader()
, the next lifecycle method is BeanFactoryAware#setBeanFactory()
.
πΉ What Happens at This Stage?
- After setting the bean name and class loader, Spring calls
setBeanFactory()
. - This method provides access to the
BeanFactory
, which is the core Spring container that holds all bean instances. - The bean can now programmatically retrieve other beans from the factory.
πΉ Practical Example: Accessing the BeanFactory
We've extended MyDynamicBean
to implement BeanFactoryAware
:
class MyDynamicBean implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware {
private String beanName;
private ClassLoader classLoader;
private BeanFactory beanFactory;
public MyDynamicBean() {
System.out.println("MyDynamicBean instantiated!");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("Bean name set: " + name);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
System.out.println("Bean ClassLoader set: " + classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
System.out.println("BeanFactory set: " + beanFactory);
}
}
π‘ Key Observations
β
The setBeanFactory()
method is called after setBeanClassLoader()
.
β
The BeanFactory
allows beans to retrieve other beans programmatically.
β
Best Practice: Use dependency injection (@Autowired
) instead of directly accessing BeanFactory
, unless you need dynamic lookups.
Step 6: Understanding setEnvironment()
Now that we've covered setBeanFactory()
, the next lifecycle method is EnvironmentAware#setEnvironment()
.
πΉ What Happens at This Stage?
- After setting bean factory access, Spring calls
setEnvironment()
. - This method allows the bean to access environment properties, such as:
- System properties (
System.getProperty("key")
) - Application properties (
application.properties
/application.yml
) - Profiles (
spring.profiles.active
) - Other configurable settings.
- System properties (
πΉ Practical Example: Accessing Environment Variables
We've extended MyDynamicBean
to implement EnvironmentAware
:
class MyDynamicBean implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, EnvironmentAware {
private String beanName;
private ClassLoader classLoader;
private BeanFactory beanFactory;
private Environment environment;
public MyDynamicBean() {
System.out.println("MyDynamicBean instantiated!");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("Bean name set: " + name);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
System.out.println("Bean ClassLoader set: " + classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
System.out.println("BeanFactory set: " + beanFactory);
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
System.out.println("Environment set: " + environment);
System.out.println("Active Profile: " + environment.getActiveProfiles());
System.out.println("Server Port: " + environment.getProperty("server.port"));
}
}
π‘ Key Observations
β
The setEnvironment()
method is called after setBeanFactory()
.
β
The environment allows access to application properties and profiles.
β
Best Practice: Use it when a bean needs to read environment properties dynamically.
Step 7: Understanding setEmbeddedValueResolver()
Now that we've covered setEnvironment()
, the next lifecycle method is EmbeddedValueResolverAware#setEmbeddedValueResolver()
.
πΉ What Happens at This Stage?
- After setting environment access, Spring calls
setEmbeddedValueResolver()
. - This method provides a StringValueResolver that allows resolving embedded placeholders (e.g.,
${server.port}
or#{expression}
). - Useful when dealing with property placeholders or Spring Expression Language (SpEL).
πΉ Practical Example: Resolving Embedded Placeholders
We've extended MyDynamicBean
to implement EmbeddedValueResolverAware
:
class MyDynamicBean implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, EnvironmentAware, EmbeddedValueResolverAware {
private String beanName;
private ClassLoader classLoader;
private BeanFactory beanFactory;
private Environment environment;
private StringValueResolver valueResolver;
public MyDynamicBean() {
System.out.println("MyDynamicBean instantiated!");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("Bean name set: " + name);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
System.out.println("Bean ClassLoader set: " + classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
System.out.println("BeanFactory set: " + beanFactory);
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
System.out.println("Environment set: " + environment);
System.out.println("Active Profile: " + environment.getActiveProfiles());
System.out.println("Server Port: " + environment.getProperty("server.port"));
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
String resolvedValue = valueResolver.resolveStringValue("${server.port}");
System.out.println("Embedded Value Resolved: " + resolvedValue);
}
}
π‘ Key Observations
β
The setEmbeddedValueResolver()
method is called after setEnvironment()
.
β
Allows resolving placeholders like ${property.name}
dynamically.
β
Best Practice: Use this when a bean needs to dynamically resolve properties or SpEL expressions.
Step 8: Understanding setResourceLoader()
Now that we've covered setEmbeddedValueResolver()
, the next lifecycle method is ResourceLoaderAware#setResourceLoader()
.
πΉ What Happens at This Stage?
- After setting the embedded value resolver, Spring calls
setResourceLoader()
. - This method provides a ResourceLoader, which allows loading external resources such as:
-
Files from the classpath (
classpath:/config.xml
) -
Files from the file system (
file:/tmp/config.xml
) -
URLs (
https://example.com/config.xml
)
-
Files from the classpath (
πΉ Practical Example: Accessing Resources
We've extended MyDynamicBean
to implement ResourceLoaderAware
:
class MyDynamicBean implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, EnvironmentAware, EmbeddedValueResolverAware, ResourceLoaderAware {
private String beanName;
private ClassLoader classLoader;
private BeanFactory beanFactory;
private Environment environment;
private StringValueResolver valueResolver;
private ResourceLoader resourceLoader;
public MyDynamicBean() {
System.out.println("MyDynamicBean instantiated!");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("Bean name set: " + name);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
System.out.println("Bean ClassLoader set: " + classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
System.out.println("BeanFactory set: " + beanFactory);
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
System.out.println("Environment set: " + environment);
System.out.println("Active Profile: " + environment.getActiveProfiles());
System.out.println("Server Port: " + environment.getProperty("server.port"));
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
String resolvedValue = valueResolver.resolveStringValue("${server.port}");
System.out.println("Embedded Value Resolved: " + resolvedValue);
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
System.out.println("ResourceLoader set: " + resourceLoader);
// Example usage: Load a file from classpath
System.out.println("Loading resource example: " + resourceLoader.getResource("classpath:application.properties").exists());
}
}
π‘ Key Observations
β
The setResourceLoader()
method is called after setEmbeddedValueResolver()
.
β
The ResourceLoader allows loading external files dynamically.
β
Best Practice: Use ResourceLoader
when a bean needs to load external configurations or static assets.
Step 9: Understanding setApplicationEventPublisher()
Now that we've covered setResourceLoader()
, the next lifecycle method is ApplicationEventPublisherAware#setApplicationEventPublisher()
.
πΉ What Happens at This Stage?
- After setting the resource loader, Spring calls
setApplicationEventPublisher()
. - This method provides an ApplicationEventPublisher, which allows the bean to:
- Publish custom application events.
-
Trigger event-driven components (such as
@EventListener
).
πΉ Practical Example: Publishing an Event
We've extended MyDynamicBean
to implement ApplicationEventPublisherAware
:
class MyDynamicBean implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, EnvironmentAware, EmbeddedValueResolverAware, ResourceLoaderAware, ApplicationEventPublisherAware {
private String beanName;
private ClassLoader classLoader;
private BeanFactory beanFactory;
private Environment environment;
private StringValueResolver valueResolver;
private ResourceLoader resourceLoader;
private ApplicationEventPublisher eventPublisher;
public MyDynamicBean() {
System.out.println("MyDynamicBean instantiated!");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("Bean name set: " + name);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
System.out.println("Bean ClassLoader set: " + classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
System.out.println("BeanFactory set: " + beanFactory);
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
System.out.println("Environment set: " + environment);
System.out.println("Active Profile: " + environment.getActiveProfiles());
System.out.println("Server Port: " + environment.getProperty("server.port"));
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
String resolvedValue = valueResolver.resolveStringValue("${server.port}");
System.out.println("Embedded Value Resolved: " + resolvedValue);
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
System.out.println("ResourceLoader set: " + resourceLoader);
System.out.println("Loading resource example: " + resourceLoader.getResource("classpath:application.properties").exists());
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
System.out.println("ApplicationEventPublisher set: " + eventPublisher);
// Publish a custom event
eventPublisher.publishEvent(new CustomEvent(this, "Test Event Message"));
}
}
// Custom Event Class
class CustomEvent extends org.springframework.context.ApplicationEvent {
private final String message;
public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
// Listener for Custom Events
@Component
class CustomEventListener {
@org.springframework.context.event.EventListener
public void handleCustomEvent(CustomEvent event) {
System.out.println("Received Custom Event: " + event.getMessage());
}
}
π‘ Key Observations
β
The setApplicationEventPublisher()
method is called after setResourceLoader()
.
β
The ApplicationEventPublisher allows beans to trigger application events.
β
Best Practice: Use ApplicationEventPublisher
for decoupling components via event-driven programming.
Step 10: Understanding setMessageSource()
Now that we've covered setApplicationEventPublisher()
, the next lifecycle method is MessageSourceAware#setMessageSource()
.
πΉ What Happens at This Stage?
- After setting the ApplicationEventPublisher, Spring calls
setMessageSource()
. - This method provides a MessageSource, which allows beans to:
- Retrieve localized messages (for internationalization).
-
Access
messages.properties
files for text translations.
πΉ Practical Example: Retrieving Messages
We've extended MyDynamicBean
to implement MessageSourceAware
:
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
System.out.println("MessageSource set: " + messageSource);
// Retrieve a message from messages.properties (if configured)
System.out.println("Example message: " + messageSource.getMessage("example.message", null, "Default Message", null));
}
π‘ Key Observations
β
The setMessageSource()
method is called after setApplicationEventPublisher()
.
β
MessageSource allows retrieving localized messages dynamically.
β
Best Practice: Use MessageSource
for internationalization and text localization.
Step 11: Understanding setApplicationContext()
Now that we've covered setMessageSource()
, the next lifecycle method is ApplicationContextAware#setApplicationContext()
.
πΉ What Happens at This Stage?
- After setting the MessageSource, Spring calls
setApplicationContext()
. - This method provides an ApplicationContext, which allows beans to:
- Access all beans in the Spring container.
- Retrieve environment properties.
- Interact with the lifecycle of the entire application.
πΉ Practical Example: Accessing the ApplicationContext
We've extended MyDynamicBean
to implement ApplicationContextAware
:
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("ApplicationContext set: " + applicationContext);
// Example: Retrieving a bean dynamically
MyDynamicBean anotherBean = applicationContext.getBean(MyDynamicBean.class);
System.out.println("Retrieved bean from ApplicationContext: " + anotherBean);
}
π‘ Key Observations
β
The setApplicationContext()
method is called after setMessageSource()
.
β
ApplicationContext provides access to all registered beans dynamically.
β
Best Practice: Use @Autowired
instead of manually accessing the ApplicationContext
, unless necessary.
Step 12: Understanding setServletContext()
Now that we've covered setApplicationContext()
, the next lifecycle method is ServletContextAware#setServletContext()
(applicable for web applications).
πΉ What Happens at This Stage?
- After setting the ApplicationContext, Spring calls
setServletContext()
. - This method provides access to the ServletContext, allowing the bean to:
- Interact with web-related resources.
- Retrieve application-wide configuration parameters.
- Access web-related objects like request/session attributes.
πΉ Practical Example: Accessing the ServletContext
We've extended MyDynamicBean
to implement ServletContextAware
:
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
System.out.println("ServletContext set: " + servletContext);
// Example: Retrieving a context parameter
String contextParam = servletContext.getInitParameter("someConfig");
System.out.println("Context Parameter: " + contextParam);
}
π‘ Key Observations
β
The setServletContext()
method is called after setApplicationContext()
.
β
ServletContext allows interaction with web-specific components.
β
Best Practice: Use ServletContext
only when necessary; prefer Spring beans for managing web configurations.
Step 13: Understanding postProcessBeforeInitialization()
Now that we've covered setServletContext()
, the next lifecycle method is BeanPostProcessor#postProcessBeforeInitialization()
.
πΉ What Happens at This Stage?
- Spring calls
postProcessBeforeInitialization()
before the initialization callbacks (@PostConstruct
,InitializingBean#afterPropertiesSet()
). - This method allows beans to:
- Modify bean properties before initialization.
- Apply proxying, validation, or logging before initialization.
πΉ Practical Example: Intercepting Bean Initialization
We've added a BeanPostProcessor to modify beans before initialization:
@Component
class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization called for: " + beanName);
return bean;
}
}
π‘ Key Observations
β
The postProcessBeforeInitialization()
method is called before @PostConstruct
and afterPropertiesSet()
.
β
Best Practice: Use this for bean modifications, logging, or applying custom initialization logic.
Step 14: Understanding @PostConstruct
and afterPropertiesSet()
Now that we've covered postProcessBeforeInitialization()
, the next lifecycle methods are @PostConstruct
and InitializingBean#afterPropertiesSet()
.
πΉ What Happens at This Stage?
- Spring calls initialization callbacks after
postProcessBeforeInitialization()
, including:-
@PostConstruct
: A method annotated with@PostConstruct
runs after dependency injection is completed. -
afterPropertiesSet()
: This method is part of theInitializingBean
interface and runs after all properties are set.
-
- These methods are typically used for:
- Validating required properties.
- Setting up resources after dependency injection.
πΉ Practical Example: Using @PostConstruct
and afterPropertiesSet()
We've modified MyDynamicBean
to implement InitializingBean
and added a @PostConstruct
method:
@PostConstruct
public void postConstruct() {
System.out.println("@PostConstruct method executed");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet() method executed");
}
π‘ Key Observations
β
@PostConstruct
runs before afterPropertiesSet()
.
β
Best Practice: Use @PostConstruct
for setup logic, and afterPropertiesSet()
when implementing InitializingBean
.
β
Avoid implementing InitializingBean
unless absolutely necessary, as @PostConstruct
is more flexible.
Step 15: Understanding postProcessAfterInitialization()
Now that we've covered @PostConstruct
and afterPropertiesSet()
, the next lifecycle method is BeanPostProcessor#postProcessAfterInitialization()
.
πΉ What Happens at This Stage?
- Spring calls
postProcessAfterInitialization()
after initialization callbacks (@PostConstruct
,afterPropertiesSet()
). - This method allows:
- Applying proxying mechanisms (e.g., AOP).
- Performing post-initialization modifications (e.g., wrapping the bean).
πΉ Practical Example: Intercepting Bean Post-Initialization
We've extended CustomBeanPostProcessor
to implement postProcessAfterInitialization()
:
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization called for: " + beanName);
return bean;
}
π‘ Key Observations
β
The postProcessAfterInitialization()
method is called after @PostConstruct
and afterPropertiesSet()
.
β
Best Practice: Use this method for applying proxies or final modifications after the bean is fully initialized.
Step 16: Understanding @PreDestroy
and destroy()
Now that we've covered postProcessAfterInitialization()
, the final lifecycle methods are @PreDestroy
and DisposableBean#destroy()
.
πΉ What Happens at This Stage?
- Spring calls destruction callbacks before shutting down the application.
- These methods ensure proper resource cleanup:
-
@PreDestroy
: Runs before the bean is destroyed, ideal for closing resources. -
destroy()
: FromDisposableBean
, allows executing destruction logic.
-
πΉ Practical Example: Cleaning Up Resources
We've modified MyDynamicBean
to implement DisposableBean
and added a @PreDestroy
method:
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy method executed");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy() method executed");
}
π‘ Key Observations
β
@PreDestroy
runs before destroy()
.
β
Best Practice: Use @PreDestroy
for graceful shutdown and destroy()
if implementing DisposableBean
.
β
Avoid implementing DisposableBean
unless required, as @PreDestroy
is more flexible.
Happy coding π
Top comments (0)