Unlock Spring Boot’s Hidden Extension Points: A Complete Guide to Bean Lifecycle Hooks
This article walks through every extensible hook in Spring and Spring Boot—from container refresh and ApplicationContextInitializer to BeanFactoryPostProcessor, InstantiationAwareBeanPostProcessor, SmartInitializingSingleton, and more—showing when each runs, how to implement it, and practical code examples for Java developers.
Background
Spring’s core idea is a container; when the container refreshes, the outside appears calm while the inside undergoes intense activity. Spring Boot further packages Spring, follows convention‑over‑configuration, and adds auto‑configuration, allowing most features to work with almost zero setup.
The author loves this auto‑configuration mechanism and uses it when developing middleware and common libraries, aiming to let users integrate with minimal effort. Understanding bean construction lifecycle and extension interfaces is essential to master auto‑configuration and write cleaner code.
Extension Interface Invocation Order Diagram
The following diagram (kept as an image) shows the complete order of all extensible points during a bean’s lifecycle inside the Spring container.
ApplicationContextInitializer
The
ApplicationContextInitializerinterface is a callback invoked before the Spring container refreshes. Implementations can run custom logic before any beans are instantiated, such as activating early configurations or performing dynamic byte‑code injection.
<code>public class TestApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("[ApplicationContextInitializer]");
}
}</code>Three ways to register it:
Programmatically via
springApplication.addInitializers(new TestApplicationContextInitializer())In a properties file:
context.initializer.classes=com.example.demo.TestApplicationContextInitializerVia Spring SPI in
spring.factories:
org.springframework.context.ApplicationContextInitializer=com.example.demo.TestApplicationContextInitializerBeanDefinitionRegistryPostProcessor
This interface runs after bean definitions are read, allowing dynamic registration of additional bean definitions, even from outside the classpath.
<code>public class TestBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanDefinitionRegistry");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanFactory");
}
}</code>BeanFactoryPostProcessor
Runs after bean definitions are loaded but before any bean is instantiated, enabling modification of bean definition metadata.
<code>public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("[BeanFactoryPostProcessor]");
}
}</code>InstantiationAwareBeanPostProcessor
Extends
BeanPostProcessorand adds three methods that operate during the instantiation phase and property injection phase, giving fine‑grained control over bean creation.
<code>public class TestInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] before initialization " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] after initialization " + beanName);
return bean;
}
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] before instantiation " + beanName);
return null;
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] after instantiation " + beanName);
return true;
}
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
System.out.println("[TestInstantiationAwareBeanPostProcessor] postProcessPropertyValues " + beanName);
return pvs;
}
}</code>SmartInstantiationAwareBeanPostProcessor
Provides three additional callbacks:
predictBeanType,
determineCandidateConstructors, and
getEarlyBeanReference, useful for type prediction, custom constructor selection, and early bean exposure in circular‑dependency scenarios.
<code>public class TestSmartInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
@Override
public Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] predictBeanType " + beanName);
return beanClass;
}
@Override
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] determineCandidateConstructors " + beanName);
return null;
}
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] getEarlyBeanReference " + beanName);
return bean;
}
}</code>BeanFactoryAware
Allows a bean to obtain the owning
BeanFactoryafter instantiation but before property injection.
<code>public class TestBeanFactoryAware implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("[TestBeanFactoryAware] " + beanFactory.getBean(TestBeanFactoryAware.class).getClass().getSimpleName());
}
}</code>ApplicationContextAwareProcessor
Although the class itself has no direct extension point, it internally triggers six Aware interfaces after bean instantiation and before initialization, such as
EnvironmentAware,
EmbeddedValueResolverAware,
ResourceLoaderAware,
ApplicationEventPublisherAware,
MessageSourceAware, and
ApplicationContextAware. These enable beans to access environment variables, resource loaders, event publishers, message sources, and the application context itself.
BeanNameAware
Provides a single callback
setBeanNamebefore bean initialization, allowing custom handling of the bean’s registration name.
<code>public class NormalBeanA implements BeanNameAware {
@Override
public void setBeanName(String name) {
System.out.println("[BeanNameAware] " + name);
}
}</code>@PostConstruct
Marks a method to be invoked after bean properties are set but before the bean is fully initialized, i.e., after
postProcessBeforeInitializationand before
InitializingBean.afterPropertiesSet.
<code>public class NormalBeanA {
@PostConstruct
public void init() {
System.out.println("[PostConstruct] NormalBeanA");
}
}</code>InitializingBean
Provides the
afterPropertiesSetmethod, called after bean properties are set and before the bean is fully initialized.
<code>public class NormalBeanA implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("[InitializingBean] NormalBeanA");
}
}</code>FactoryBean
Allows custom creation of complex beans. Implementations can return any object, define its type, and specify singleton status.
<code>public class TestFactoryBean implements FactoryBean<TestFactoryBean.TestFactoryInnerBean> {
@Override
public TestFactoryInnerBean getObject() throws Exception {
System.out.println("[FactoryBean] getObject");
return new TestFactoryInnerBean();
}
@Override
public Class<?> getObjectType() { return TestFactoryInnerBean.class; }
@Override
public boolean isSingleton() { return true; }
public static class TestFactoryInnerBean {}
}</code>SmartInitializingSingleton
Callback
afterSingletonsInstantiatedruns after all non‑lazy singleton beans have been created.
<code>public class TestSmartInitializingSingleton implements SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
System.out.println("[TestSmartInitializingSingleton]");
}
}</code>CommandLineRunner
Runs after the application context is fully started; multiple runners can be ordered with
@Order.
<code>public class TestCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("[TestCommandLineRunner]");
}
}</code>DisposableBean
Provides a
destroymethod invoked when the bean is being destroyed, e.g., during application shutdown.
<code>public class NormalBeanA implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("[DisposableBean] NormalBeanA");
}
}</code>ApplicationListener
Can listen to Spring’s built‑in events such as
ContextRefreshedEvent,
ContextStartedEvent,
ContextStoppedEvent,
ContextClosedEvent, and
RequestHandledEvent, enabling custom actions at various lifecycle stages.
Conclusion
By understanding and leveraging these Spring and Spring Boot extension points, developers can intervene at virtually any stage of the bean lifecycle, customizing initialization, registration, and cleanup to suit middleware or business‑level requirements.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.