Backend Development 18 min read

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.

macrozheng
macrozheng
macrozheng
Unlock Spring Boot’s Hidden Extension Points: A Complete Guide to Bean Lifecycle Hooks

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.

Bean lifecycle extension points diagram
Bean lifecycle extension points diagram

ApplicationContextInitializer

The

ApplicationContextInitializer

interface 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&lt;ConfigurableApplicationContext&gt; {
    @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.TestApplicationContextInitializer

Via Spring SPI in

spring.factories

:

org.springframework.context.ApplicationContextInitializer=com.example.demo.TestApplicationContextInitializer

BeanDefinitionRegistryPostProcessor

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

BeanPostProcessor

and 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

BeanFactory

after 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.

ApplicationContextAwareProcessor diagram
ApplicationContextAwareProcessor diagram

BeanNameAware

Provides a single callback

setBeanName

before 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

postProcessBeforeInitialization

and before

InitializingBean.afterPropertiesSet

.

<code>public class NormalBeanA {
    @PostConstruct
    public void init() {
        System.out.println("[PostConstruct] NormalBeanA");
    }
}</code>

InitializingBean

Provides the

afterPropertiesSet

method, 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

afterSingletonsInstantiated

runs 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

destroy

method 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.

JavaSpring BootExtension PointsSpring Frameworkbean lifecycle
macrozheng
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.