Spring Bean 初始化之InitializingBean, init-method 和 PostConstruct
阿新 • • 發佈:2019-02-05
概述
從介面的名字上不難發現,InitializingBean 的作用就是在 bean 初始化後執行定製化的操作。
Spring 容器中的 Bean 是有生命週期的,Spring 允許在 Bean 在初始化完成後以及 Bean 銷燬前執行特定的操作,常用的設定方式有以下三種:
- 通過實現 InitializingBean/DisposableBean 介面來定製初始化之後/銷燬之前的操作方法;
- 通過 <bean> 元素的 init-method/destroy-method 屬性指定初始化之後 /銷燬之前呼叫的操作方法;
- 在指定方法上加上@PostConstruct 或@PreDestroy註解來制定該方法是在初始化之後還是銷燬之前呼叫。
注:以下原始碼分析基於spring 5.0.4
InitializingBean vs init-method
介面定義如下:
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
介面只有一個方法afterPropertiesSet
,此方法的呼叫入口是負責載入 spring bean 的AbstractAutowireCapableBeanFactory
,原始碼如下:
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
((InitializingBean) bean).afterPropertiesSet();
return null;
}, getAccessControlContext());
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
((InitializingBean) bean).afterPropertiesSet();
}
}
從這段原始碼可以得出以下結論:
- spring為bean提供了兩種初始化bean的方式,實現InitializingBean介面,實現afterPropertiesSet方法,或者在配置檔案中通過init-method指定,兩種方式可以同時使用
- 實現InitializingBean介面是直接呼叫afterPropertiesSet方法,比通過反射呼叫init-method指定的方法效率相對來說要高點。但是init-method方式消除了對spring的依賴
- 先呼叫afterPropertiesSet,再執行 init-method 方法,如果呼叫afterPropertiesSet方法時出錯,則不呼叫init-method指定的方法。
@PostConstruct
通過 debug 和呼叫棧找到類InitDestroyAnnotationBeanPostProcessor
, 其中的核心方法,即 @PostConstruct
方法呼叫的入口:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
metadata.invokeInitMethods(bean, beanName);
}
catch (InvocationTargetException ex) {
throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
}
return bean;
}
從命名上,我們就可以得到某些資訊——這是一個BeanPostProcessor。想到了什麼?在也談Spring容器的生命週期中,提到過BeanPostProcessor的postProcessBeforeInitialization是在Bean生命週期中afterPropertiesSet和init-method之前被呼叫的。另外通過跟蹤,@PostConstruct
方法的呼叫方式也是通過發射機制。
總結
- spring bean的初始化執行順序:構造方法 -->
@PostConstruct
註解的方法 -->afterPropertiesSet
方法 -->init-method
指定的方法。具體可以參考例子 afterPropertiesSet
通過介面實現方式呼叫(效率上高一點),@PostConstruct
和init-method
都是通過反射機制呼叫
例子
直接執行單測com.skyarthur.springboot.common.bean.InitSequenceBeanTest
, 請戳程式碼下載地址
核心程式碼如下:
@Slf4j
public class InitSequenceBean implements InitializingBean {
public InitSequenceBean() {
log.info("InitSequenceBean: construct");
}
@Override
public void afterPropertiesSet() throws Exception {
log.info("InitSequenceBean: afterPropertiesSet");
}
@PostConstruct
public void postConstruct() {
log.info("InitSequenceBean: postConstruct");
}
public void initMethod() {
log.info("InitSequenceBean: initMethod");
}
}
@Configuration
public class SystemConfig {
@Bean(initMethod = "initMethod", name = "initSequenceBean")
public InitSequenceBean initSequenceBean() {
return new InitSequenceBean();
}
}
@Slf4j
public class InitSequenceBeanTest extends ApplicationTests {
@Autowired
private InitSequenceBean initSequenceBean;
@Test
public void initSequenceBeanTest() {
log.info("Finish: {}", initSequenceBean.toString());
}
}