Master Spring Bean Lifecycle: BeanPostProcessor, @PostConstruct & InitializingBean
This article explains how Spring's bean lifecycle can be customized using BeanPostProcessor, @PostConstruct, InitializingBean, and initMethod, providing code examples, execution logs, and best‑practice scenarios for extending bean creation and initialization in backend Java applications.
Background
Spring provides many extension points that let you customize bean creation beyond simple getter/setter or constructor injection. Examining frameworks built on Spring, such as Spring Cloud Netflix and Spring Cloud Alibaba, reveals numerous extension beans like Eureka health checks and Seata Feign configuration.
<code>package org.springframework.cloud.netflix.eureka;
public class EurekaHealthCheckHandler implements InitializingBean {}
</code> <code>package com.alibaba.cloud.seata.feign;
public class SeataContextBeanPostProcessor implements BeanPostProcessor {}
</code>Code Example
<code>@Slf4j
public class DemoBean implements InitializingBean {
public DemoBean() {
log.info("--> instantiate ");
}
@PostConstruct
public void postConstruct() {
log.info("--> @PostConstruct ");
}
@Override
public void afterPropertiesSet() throws Exception {
log.info("--> InitializingBean.afterPropertiesSet ");
}
public void initMethod() {
log.info("--> custom initMehotd");
}
}
</code> <code>@Configuration
public class DemoBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if ("demoBean".equals(beanName)) {
log.info("--> BeanPostProcessor.postProcessBeforeInitialization ");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("demoBean".equals(beanName)) {
log.info("--> BeanPostProcessor.postProcessAfterInitialization ");
}
return bean;
}
}
</code> <code>@Configuration
public class DemoConfig {
@Bean(initMethod = "initMethod")
public DemoBean demoBean() {
return new DemoBean();
}
}
</code>Runtime Log Output
<code>DemoBean : --> instantiate
DemoBeanPostProcessor : --> BeanPostProcessor.postProcessBeforeInitialization
DemoBean : --> @PostConstruct
DemoBean : --> InitializingBean.afterPropertiesSet
DemoBean : --> custom initMehotd
DemoBeanPostProcessor : --> BeanPostProcessor.postProcessAfterInitialization
</code>Core Source Code of Execution Process
<code>protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
// execute BeanPostProcessor.postProcessBeforeInitialization
Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
// ...
// execute user-defined init methods and JSR‑250 methods
invokeInitMethods(beanName, wrappedBean, mbd);
// ...
// execute BeanPostProcessor.postProcessAfterInitialization
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
</code>BeanPostProcessor
BeanPostProcessor is an interface that lets you implement custom callbacks for bean instantiation, dependency resolution, and other lifecycle steps. It is often used with the Adapter pattern to wrap or transform beans before and after creation.
<code>// seata context conversion, wrap other types into SeataFeignContext
public class SeataContextBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof FeignContext && !(bean instanceof SeataFeignContext)) {
return new SeataFeignContext(getSeataFeignObjectWrapper(), (FeignContext) bean);
}
return bean;
}
}
</code>PostConstruct
@PostConstruct, introduced in Java EE 5, is applied to a method that runs after dependency injection but before the bean is put into service. Only one non‑static method per class may use this annotation, it must have no parameters, no return value, and must not throw checked exceptions. In Java 11 the annotation is no longer part of the JDK and must be added via the
javax.annotation-apidependency.
<code><dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
</code>Although @PostConstruct was once a convenient way to run initialization logic, newer Java versions encourage using Spring events or custom init methods instead.
InitializingBean
The
InitializingBeaninterface defines a callback that runs after all bean properties have been set. It is useful for dynamically adjusting injected parameters or adding default values that are not supplied by the user.
<code>@ConfigurationProperties(prefix = "security.oauth2")
public class PermitAllUrlProperties {
@Getter @Setter
private List<String> ignoreUrls = new ArrayList<>();
}
</code>When additional, non‑user‑maintained configuration is needed, implementing
InitializingBeanallows you to augment the bean after property injection.
<code>@ConfigurationProperties(prefix = "security.oauth2.ignore")
public class PermitAllUrlProperties implements InitializingBean {
@Getter @Setter
private List<String> urls = new ArrayList<>();
@Override
public void afterPropertiesSet() {
urls.add("/common/*");
}
}
</code>initMethod
Instead of @PostConstruct, you can declare an init method directly on a @Bean definition.
<code>@Bean(initMethod = "initMethod")
public DemoBean demoBean() {
return new DemoBean();
}
public void initMethod() {
log.info("--> custom initMehotd");
}
</code>Summary
Reference: Spring Bean Factory Documentation
mica project: https://github.com/lets-mica/mica
This article covered common extension points in the Spring bean creation cycle; a follow‑up will discuss destruction‑phase extensions.
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
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.