InitializingBean, init-method 和 PostConstruct
1、概述
从接口的名字上不难发现,InitializingBean 的作用就是在 bean 初始化后执行定制化的操作。
Spring 容器中的 Bean 是有生命周期的,Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下三种:
- 通过实现 InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
- 通过 <bean> 元素的 init-method/destroy-method 属性指定初始化之后 /销毁之前调用的操作方法;
- 在指定方法上加上@PostConstruct 或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用。
2、InitializingBean vs init-method
接口定义如下:
public interface InitializingBean {void afterPropertiesSet() throws Exception;
}
接口只有一个方法afterPropertiesSet,
此方法的调用入口是负责加载 spring bean 的AbstractAutowireCapableBeanFactory,源码如下:
protected void invokeInitMethods(String beanName, Object bean,RootBeanDefinition mbd) throws Throwable {boolean isInitializingBean = bean instanceof InitializingBean;if ((isInitializingBean)&& (((mbd == null) || (!(mbd.isExternallyManagedInitMethod("afterPropertiesSet")))))) {if (this.logger.isDebugEnabled()) {this.logger.debug("Invoking afterPropertiesSet() on bean with name '"+ beanName + "'");}//先调用afterPropertiesSet()进行初始化if (System.getSecurityManager() != null) {try {AccessController.doPrivileged(new PrivilegedExceptionAction(bean) {public Object run() throws Exception {((InitializingBean) this.val$bean).afterPropertiesSet();return null;}}, getAccessControlContext());} catch (PrivilegedActionException pae) {throw pae.getException();}} else {((InitializingBean) bean).afterPropertiesSet();}}//然后调用InitMethod()进行初始化if (mbd != null) {String initMethodName = mbd.getInitMethodName();if ((initMethodName == null)|| ((isInitializingBean) && ("afterPropertiesSet".equals(initMethodName)))|| (mbd.isExternallyManagedInitMethod(initMethodName)))return;invokeCustomInitMethod(beanName, bean, mbd);}}
从这段源码可以得出以下结论:
- spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用
- 实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率相对来说要高点。但是init-method方式消除了对spring的依赖
- 先调用afterPropertiesSet,再执行 init-method 方法,如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法
3、@PostConstruct
通过 debug 和调用栈找到类InitDestroyAnnotationBeanPostProcessor, 其中的核心方法,即 @PostConstruct 方法调用的入口:
@Overridepublic 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。BeanPostProcessor的postProcessBeforeInitialization是在Bean生命周期中afterPropertiesSet和init-method之前被调用的。另外通过跟踪,@PostConstruct方法的调用方式也是通过反射机制。
4、总结
- spring bean的初始化执行顺序:构造方法 --> @PostConstruct注解的方法 --> afterPropertiesSet方法 --> init-method指定的方法。具体可以参考例子
- afterPropertiesSet通过接口实现方式调用(效率上高一点),@PostConstruct和init-method都是通过反射机制调用
同理,bean销毁过程的顺序为:@PreDestroy > DisposableBean > destroy-method
不再展开,看源码就好
测试代码如下:
@Slf4j
public class InitSequenceBean implements InitializingBean {public InitSequenceBean() {log.info("InitSequenceBean: construct");}@Overridepublic void afterPropertiesSet() throws Exception {log.info("InitSequenceBean: afterPropertiesSet");}@PostConstructpublic 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 {@Autowiredprivate InitSequenceBean initSequenceBean;@Testpublic void initSequenceBeanTest() {log.info("Finish: {}", initSequenceBean.toString());}
}