从@PostConstruct重新认识初始化
一、背景
1.启动报错
有前端在调后端测试环境接口的时候反馈某个服务挂了,然后去机器上看了下。通过ps命令和supervisor工具检查进程在:
ps -ef | grep xxx
supervisorctl status xxx
但是通过netstat命令查看端口没有监听成功:
netstat -tunlp | grep port
怀疑springboot启动卡住了,查看日志看到刷屏报错:
代码中查看报错位置,发现某个类使用了@PostConstruct初始化,并且初始化方法调用了异步任务:
@PostConstruct
public void init() {
a.process();
}
@Async("taskExecutor")
public void process() {
while (true) {
try {
MessageDTO message = redisManager.leftPop(key, MessageDTO.class);
if (message != null) {
log.info("[处理事件,{}", message);
b.doSomething( message);
}
if (message == null) {
ThreadUtils.sleep(1000L);
}
} catch (Exception e) {
log.error("消息处理失败,error={}", message, e);
ThreadUtils.sleep(1000L);
}
}
}
查看开始报错位置,从启动日志中看到:
从报错信息中可以看到,有个bean的@Autowired属性注入失败了,但是其他bean用@PostConstruct标注的方法继续执行了。
2.回到初始化
很多时候,我们在服务启动完成会做一些初始化动作,比如加载数据,前置计算和服务预热等等,我理解执行这些动作的时机应该放到容器上下文、bean以及servlet容器都已经初始化完成之后,不然会出现一些不完整操作和其他一些不可预知的影响。
二、@PostConstruct初始化存在的问题
1.为什么@Autowired注入失败@PostConstruct会继续执行
springboot启动时会调用AbstractApplicationContext的refresh方法:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//省略...
try {
//省略...
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
destroyBeans();
cancelRefresh(ex);
throw ex;
}
}
}
此处会做普通bean的实例化和初始化,在try catch中也看到了前边报错截图中的内容:
Exception encountered during context initialization - " +
"cancelling refresh attempt:
实例化和初始化bean调用前边的finishBeanFactoryInitialization方法,然后会调用到BeanFactory的preInstantiateSingletons方法:
public void preInstantiateSingletons() throws BeansException {
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 初始化bean
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}
// 回调实现了SmartInitializingSingleton接口的bean
//暂时省略,后续会用到
}
这里有个关键点,先从BeanFactory中拿到所有的BeanDefination,然后遍历进行顺序实例化和初始化,也就是挨个调用getBean方法,最终创建bean会调用AbstractAutowireCapableBeanFactory的doCreateBean方法:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
...省略部分代码
// 1.实例化bean
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// 2.允许后处理器修改合并的bean定义
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
...省略部分代码
// 3.初始化bean
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
...省略部分代码
// 4.注册销毁逻辑
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
@Autowired在populateBean方法中实现,@PostConstruct在initializeBean方法中实现。从之前文章@Autowired注解原理分析和Spring bean生命周期管理两篇文章可以知道,@Autowired和@PostConstruct逻辑分别由AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor实现,执行顺序AutowiredAnnotationBeanPostProcessor在前,CommonAnnotationBeanPostProcessor在后。 那么既然@Autowired和@PostConstruct执行顺序有先后关系,那么为什么还会出现@Autowired注入失败@PostConstruct会仍旧继续执行呢个? 前边preInstantiateSingletons方法执行逻辑是拿到BeanDefination列表后循环执行的,在我们代码中定义用@Service、@Component和@Repository定义的类在启动阶段定义成BeanDefination时,容器会组装bean之间的依赖树,然后循环实例初始化。为了更清晰的分析问题,我们定义三个bean:
@Service
class A {
@Autowired
B b;
@PostConstruct
void init() {
log.info("a execute init ...");
}
}
@Component
class B {
B() {
throw RuntimeException("instant failed");
}
}
@Service
class C {
@PostConstruct
void init() {
log.info("c execute init...");
}
}
上述代码中,A通过@Autowired注入B,但是B实例化失败,由于对于A实例化和初始化会先注入B,然后执行@PostConstruct,由于注入失败,所以不会再执行@PostConstruct初始化方法。 而对于C,如果他的实例化和初始化是在A之前,也即是preInstantiateSingletons对C的操作在A之前,那么C的@PostConstruct方法就会被触发。 也就验证了为什么@Autowired注入失败@PostConstruct还会继续执行的问题。
2.既然@PostConstruct执行了,为什么会报错?
从刚开始的启动报错截图中,就是cancelling refresh attempt报错后,@PostConstruct方法继续确实执行了,我们看到还有一个现象就是@PostConstruct执行报错,那么既然执行了为什么还会报错呢? 按照前边分析的,我们把示例代码改造一下:
@Service
class A {
@Autowired
B b;
@PostConstruct
void init() {
log.info("a execute init ...");
}
}
@Component
class B {
B() {
throw RuntimeException("instant failed");
}
}
@Service
class C {
@Autowired
D d;
@PostConstruct
void init() {
for(int i = 0;i < 100;i++) {
d.doSomething();
}
}
}
@Component
class D {
void doSomething();
}
改造后C中注入了D,并且通过@PostConstruct初始化调用D的逻辑,那么C的init方法会报错吗?答案是会。 我们把AbstractApplicationContext的refresh方法中异常处理逻辑拎出来单独分析一下:
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
先打印上下文初始化失败,取消刷新尝试,然后销毁bean,会调用DefaultSingletonBeanRegistry的destroySingletons销毁容器中的bean:
public void destroySingletons() {
if (logger.isTraceEnabled()) {
logger.trace("Destroying singletons in " + this);
}
synchronized (this.singletonObjects) {
this.singletonsCurrentlyInDestruction = true;
}
String[] disposableBeanNames;
synchronized (this.disposableBeans) {
disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
}
for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
destroySingleton(disposableBeanNames[i]);
}
this.containedBeanMap.clear();
this.dependentBeanMap.clear();
this.dependenciesForBeanMap.clear();
clearSingletonCache();
}
主要做的事情就是将容器中的bean移除掉,然后清空缓存,这个时候如果前边的@PostConstruct方法还在执行并且有bean依赖的情况下,执行就会报错了。
3.为什么进程在,端口监听失败?
其实这个问题可以拆解一些,springboot启动卡住了没有启动成功也没有关机。 没有启动成功的原因是容器上下文刷新失败,正常情况下不会去监听端口,也不会启动成功。而容器上下文没有正常关闭的原因是,虽然刷新异常去销毁bean并且尝试去关闭,但是有异步线程一直在执行,负责容器启停的主线程一直在等待异步线程执行结束释放资源然后关闭,主线程被卡死了。
三、springboot更合理的初始化
回过头来思考一下,怎样做才能更合理的做好初始化动作,当然也是有一些标准可以做参考的。
- 所有的bean都实例化完成
- 所有的bean都初始化完成
- 上下文刷新完成
这个时候执行初始化动作,至少能够容器中所有的bean都已经初始化完成并且是可用的。那么有以下几种推荐的方式来实现springboot应用启动初始化动作。
1.实现ApplicationRunner和CommandLineRunner
springboot项目可以通过实现ApplicationRunner和CommandLineRunner接口来实现初始化能力,伪代码如下:
@Component
public class TestInitializer implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// 执行初始化逻辑
}
}
@Component
public class TestInitializer implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// 执行初始化逻辑
}
}
他们会在SpringApplication的run方法中被调用:
public ConfigurableApplicationContext run(String... args) {
try {
//省略...
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
return context;
}
刷新完上下文会调用callRunners方法:
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
callRunners方法会调用实现了ApplicationRunner或CommandLineRunner接口的bean的run方法来时先初始化动作。
2.监听ApplicationReadyEvent事件
SpringApplication执行run方法刷新完上下文返回调用之前还执行了一段如下逻辑:
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
然后调用内部ready方法:
void ready(ConfigurableApplicationContext context, Duration timeTaken) {
doWithListeners("spring.boot.application.ready", (listener) -> listener.ready(context, timeTaken));
}
接着调用EventPublishingRunListener�的ready方法发送事件通知:
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}
那么我们可以实现监听器,监听ApplicationReadyEvent事件就能做一些初始化的事情:
@Component
public class TestInitializer implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// 执行初始化逻辑
}
}
或者
@Component
public class TestInitializer {
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
// 执行初始化逻辑
}
}
3.实现SmartLifecycle接口
前边AbstractApplicationContext刷新上下文refresh方法中初始化bean完成后会调用finishRefresh方法:
protected void finishRefresh() {
//省略...
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
//省略...
}
此处会调用DefaultLifecycleProcessor的onRefresh做通知,以及发布ContextRefreshedEvent事件,我们先看一下DefaultLifecycleProcessor的onRefresh实现:
public void onRefresh() {
startBeans(true);
this.running = true;
}
调用内部startBeans方法:
private void startBeans(boolean autoStartupOnly) {
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
Map<Integer, LifecycleGroup> phases = new TreeMap<>();
lifecycleBeans.forEach((beanName, bean) -> {
if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) {
int phase = getPhase(bean);
phases.computeIfAbsent(
phase,
p -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly)
).add(beanName, bean);
}
});
if (!phases.isEmpty()) {
phases.values().forEach(LifecycleGroup::start);
}
}
找到实现SmartLifecycle接口的bean,组装并调用LifecycleGroup的start方法,最终会执行DefaultLifecycleProcessor的doStart方法:
private void doStart(Map<String, ? extends Lifecycle> lifecycleBeans, String beanName, boolean autoStartupOnly) {
Lifecycle bean = lifecycleBeans.remove(beanName);
if (bean != null && bean != this) {
String[] dependenciesForBean = getBeanFactory().getDependenciesForBean(beanName);
for (String dependency : dependenciesForBean) {
doStart(lifecycleBeans, dependency, autoStartupOnly);
}
if (!bean.isRunning() &&
(!autoStartupOnly || !(bean instanceof SmartLifecycle) || ((SmartLifecycle) bean).isAutoStartup())) {
try {
bean.start();
}
catch (Throwable ex) {
throw new ApplicationContextException("Failed to start bean '" + beanName + "'", ex);
}
}
}
}
然后会调用实现了SmartLifecycle接口并符合条件的bean的start方法实现初始化逻辑。 那么我们基于SmartLifecycle实现业务初始化操作,简单代码如下:
@Component
public class MyInitializer implements SmartLifecycle {
private boolean running = false;
@Override
public void start() {
// 执行初始化逻辑
running = true;
}
// 其他方法...
}
4.监听ContextStartedEvent事件
对于AbstractApplicationContext本身也实现了Lifecycle接口,在前边SmartLifecycle描述中,startBeans方法在执行start逻辑之前,有一步获取LifeCycleBean的操作:
protected Map<String, Lifecycle> getLifecycleBeans() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
Map<String, Lifecycle> beans = new LinkedHashMap<>();
//获取实现了Lifecycle接口的bean集合
String[] beanNames = beanFactory.getBeanNamesForType(Lifecycle.class, false, false);
for (String beanName : beanNames) {
String beanNameToRegister = BeanFactoryUtils.transformedBeanName(beanName);
boolean isFactoryBean = beanFactory.isFactoryBean(beanNameToRegister);
String beanNameToCheck = (isFactoryBean ? BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);
if ((beanFactory.containsSingleton(beanNameToRegister) &&
(!isFactoryBean || matchesBeanType(Lifecycle.class, beanNameToCheck, beanFactory))) ||
matchesBeanType(SmartLifecycle.class, beanNameToCheck, beanFactory)) {
Object bean = beanFactory.getBean(beanNameToCheck);
if (bean != this && bean instanceof Lifecycle) {
beans.put(beanNameToRegister, (Lifecycle) bean);
}
}
}
return beans;
}
获取到的生命周期bean是根据Lifecycle接口寻找的,也就是前边的SmartLifecycle(继承了Lifecycle)和AbstractApplicationContext都会被获取到并执行start方法,看一下AbstractApplicationContext的start方法实现:
public void start() {
getLifecycleProcessor().start();
publishEvent(new ContextStartedEvent(this));
}
这里发布了ContextStartedEvent事件,ContextStartedEvent继承了ApplicationEvent,会被实现了ApplicationListener接口的bean接收到事件并执行onApplicationEvent方法,具体事件发布和触发逻辑可以参考《事件驱动编程》spring事件驱动部分的介绍。 通过监听ContextStartedEvent事件实现初始化逻辑,简单代码如下:
@Component
public class TestContextStartedEventListener implements ApplicationListener<ContextStartedEvent> {
@Override
public void onApplicationEvent(ContextStartedEvent event) {
// 编写初始化逻辑的代码
// 当ContextStartedEvent事件发生时,该方法将被调用
// 在此处理相应的业务逻辑
}
}
5.监听ContextRefreshedEvent事件
AbstractApplicationContext刷新上下文refresh方法中初始化bean完成后调用finishRefresh方法时除了调用DefaultLifecycleProcessor的onRefresh方法之外,还会发布ContextRefreshedEvent事件:
protected void finishRefresh() {
//省略...
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));
//省略...
}
同样,ContextRefreshedEvent继承了ApplicationEvent,也会被实现了ApplicationListener接口的bean接收到事件并执行onApplicationEvent方法。基于ContextRefreshedEvent实现初始化逻辑可以简单编写如下:
@Component
public class TestContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 编写初始化逻辑的代码
// 当ContextRefreshedEvent事件发生时,该方法将被调用
// 在此处理相应的业务逻辑
}
}
6.实现ServletContextInitializer接口
AbstractApplicationContext刷新上下文的时候,会调用ServletWebServerApplicationContext的onRefresh方法:
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
创建和初始化servlet容器时会调用getSelfInitializer方法:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
然后会从容器上下文中获取实现了ServletContextInitializer接口的bean,并调用onStartup方法实现初始化操作:
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
那么基于ServletContextInitializer接口实现初始化逻辑的可参考如下代码:
public class TestServletContextInitializer implements ServletContextInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 编写初始化逻辑的代码
// 当应用启动时,该方法将被调用
// 在此处理相应的业务逻辑
}
}
7.实现SmartInitializingSingleton接口
开篇介绍AbstractApplicationContext调用refresh方法实例化bean的时候会调用preInstantiateSingletons方法,该方法除了实例化和初始化bean,还做了一些回调后置处理:
public void preInstantiateSingletons() throws BeansException {
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
//省略实例化和初始化bean的逻辑
}
// 触发所有适用bean的初始化后回调
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")
.tag("beanName", beanName);
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
smartInitialize.end();
}
}
}
上述方法中,实例化和初始化bean完成后,会获取所有实现了SmartInitializingSingleton接口的bean,然后调用其afterSingletonsInstantiated方法做一些后置处理。
那么如果我们想要在所有bean实例化和初始化完成后实现一些业务初始化逻辑,实现SmartInitializingSingleton接口即可:
@Component
public class MyInitializingSingleton implements SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
// 编写初始化逻辑的代码
// 当所有单例bean实例化完成后,该方法将被调用
// 在此处理相应的业务逻辑以及初始化
}
}
四、总结
通过上述的分析,其实基于@PostConstruct、InitializingBean接口以及自定义init方法做一些单个bean粒度的静态和局部变量初始化都没有问题,但是做一些全局初始化逻辑都是不合理的,因为bean的实例化和初始化是串行并且有顺序的,遇到已经初始化完成的bean做一些非常规的初始化动作,而后续bean实例化和初始化失败了,可能会导致一些不可预料的后果,比如前边所说的进程启动了但是端口没有监听成功,启动卡住了等等一系列问题。对于一些依赖应用上下文刷新成功或者所有bean都实例化初始化没问题的操作,建议用以上分析的几种方式做初始化操作。
转载自:https://juejin.cn/post/7249196835041509435