前言
这篇文章也是从《图与循环依赖、死锁》这个系列拆出来的,重新梳理了一下自己对Spring IOC 生命周期的理解。关于Spring 中Bean的生命周期的话,其实之前也写过一篇《Spring Bean的生命周期》,在这篇文章中我们的视角的着重点是我们实现了某个接口,继承了某个类,重写的某些方法会被执行。后面来看这个文章的缺点优点大,不够宏观,太微观的描述不利于记忆。
后面又写了《如何向Spring IOC 容器 动态注册bean》,这其实是工作的需要,一般的Spring 应用程序启动之后Bean基本都初始化完毕,但是我们需要的一些Bean在运行时加入到IOC容器里面。在这篇文章里面,我们创造了一个特殊的Bean,请求Spring注入ConfigurableApplicationContext 的实例:
@Component
public class BeanDynamicRegister02 {
private final ConfigurableApplicationContext configurableApplicationContext;
public BeanDynamicRegister02(ConfigurableApplicationContext configurableApplicationContext) {
this.configurableApplicationContext = configurableApplicationContext;
}
public void registerBean(String beanName, BeanDefinition beanDefinition){
BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) configurableApplicationContext;
beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition);
}
}
现在看看这个还得强制转换多了一步,不如一步到位直接注入DefaultListableBeanFactory:
@Component
public class BeanDynamicRegister01 {
private final DefaultListableBeanFactory defaultListableBeanFactory;
public BeanDynamicRegister01(DefaultListableBeanFactory defaultListableBeanFactory) {
this.defaultListableBeanFactory = defaultListableBeanFactory;
}
public void registerBean(String beanName, BeanDefinition beanDefinition) {
defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinition);
}
}
在《图与循环依赖、死锁(一) 为何我的循环依赖时好时坏?》我们可以看到我们在调整Bean的创建顺序的时候,我们也创建了一个Bean,不过我们让这个Bean实现了BeanFactoryPostProcessor 这个接口,然后通过MethodHandlers来改DefaultListableBeanFactory中Bean的创建顺序,来验证我们的猜想,也就是说Bean的创建顺序。 这也就是说虽然实现了BeanFactoryPostProcessor这个接口的Bean,初始化阶段更靠前一点。
本篇的核心目标就在于重塑自己对Spring IOC 生命周期的理解,强化Spring留给开发者扩展点之间的结构。
宏观来看
概述
如上图所示,可以将Spring IOC 的生命周期分为四个阶段:
-
启动和Bean定义加载:
1.1 定位配置源(xml、配置类、组件扫描)。
1.2 解析并创建BeanDefinition。
1.3 将BeanDefinition注册到DefaultListableBeanFactory
1.4 执行BeanFactoryPostProcessor
-
创建Bean阶段:
2.1 创建实例(有特殊场景,会提前调用某些特别的BeanPostProcessor 然后不执行2.2到2.6,我们不在这篇文章里面展开说明,会专门讲)
2.2 属性填充 && 依赖注入(如果是构造器注入,则创建实例和属性填充合并为一个阶段)
2.3 如果实现了Aware相关的接口 , Aware回调
2.4 如果实现了 BeanPostProcessor 进入 postProcessBeforeInitialization,此步内执行 @PostConstruct
2.5 进入InitializingBean的方法
2.6 如果实现了 BeanPostProcessor 进入 postProcessAfterInitialization
-
Bean的使用阶段:
3.1 业务代码通过DI/getBean调用
3.2 懒加载Bean在首次访问时, 才实例化
-
容器的关闭和销毁阶段:
4.1 触发close()/JVM关闭钩子
4.2 触发@PreDestrory 标记的方法,如果你的Bean实现了DisposableBean, 会触发destroy()方法。
4.3 释放资源, 等待GC
那我是如何得到上面的陈述的,在Spring的官方文档(见参考文档[1]), 有陈述,但我将这些联系了起来,下面我们将从源码中验证我们对四个阶段的定义。
启动和Bean定义
在《记一次排查循环依赖的经历》这一篇里面我们已经讲述了@ComponentScan是如何发挥作用的,每一个注解都有一个对应的类。也就是ComponentScanAnnotationParser这里,我们可以看到ComponentScanAnnotationParser的parse方法,里面去扫描包最终将我们声明Bean的元信息变成BeanDefinitionHolder:
我们接着可以看doScan的逻辑, doScan是ClassPathBeanDefinitionScanner的方法:
这个registry事实上是BeanDefinitionRegistry的实例,而BeanDefinitionRegistry又是一个接口,那我们直接在这里打断点就可以知道实际的类型:
我们接着看registerBeanDefinition方法:
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
然后接着看registerBeanDefinition的实现:
注意registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()) 这一行
现在第一个阶段的解析配置源,创建BeanDefintion,将BeanDefinition注册到DefaultListableBeanFactory。我们现在来看执行BeanFactoryPostProcessor, 在《图与循环依赖、死锁(一) 为何我的循环依赖时好时坏?》这里我们已经看到过一次了,我们实现了这个接口来在实例化Bean之前, 干扰bean的创建顺序。那我是怎么知道的呢? 第一个我们可以借助于搜索引擎,用关键词搜索 Spring 改beanDefinition。然后可以拿到我们想要的结果,也就是实现BeanFactoryPostProcessor。
但是有些网页没有给出信息来源,这不得让我们心生警惕,我们可以去BeanFactoryPostProcessor的注释去验证, 然后就在BeanFactoryPostProcessor看到了我们想要的注释:
- 容器会优先初始化实现BeanFactoryPostProcessor接口的Bean, 如果有多个BeanFactoryPostProcessor, 想要控制顺序可以通过实现
Ordered或PriorityOrdered接口。 - 在所有的BeanDefinition加载完毕之后,会触发实现了BeanFactoryPostProcessor的创建, 回调postProcessBeanFactory方法。但是不能和bean进行交互,比如getBean。不然会立刻触发那个Bean的创建。
- 可以改Bean的作用域、属性值,是否懒加载等元数据,常见的作用是替换占位符。(PropertyPlaceholderConfigurer 就实现了BeanFactoryPostProcessor,来做占位符替换)
- 和BeanPostProcessor 的区别是,BeanFactoryPostProcessor是在所有Bean实例化之前,BeanPostProcessor 是单个Bean初始化前后。
然后我们可以打断点验证看看,如果我们有多个Bean都实现了BeanFactoryPostProcessor,Spring 是如何调回调这些Bean的postProcessBeanFactory方法的:
方法堆栈如下所示:
我们就可以观察这个方法调用栈,看看是怎么获取实现了所有BeanFactoryPostProcessor的Bean的, 在PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors中我们可以看到这一点:
注意观察getBeanNamesForType的实现:
这里的allBeanNamesByType和singtonBeanNamesByType 的key是类型, value是类型对应实例的BeanName。到这里我们可以用数学的术语说一句证明完毕。
创建Bean阶段
现在我们来看创建Bean阶段的执行流程,其实这个流程我们已经在《Spring 中的AOP是如何运作的》里面已经探究过一次了, 这里我们再探究一遍,还是老思路,在构造函数上打断点观察堆栈:
┌─► ① com.xxx.service.A01TestServiceImpl.<init>(A01TestServiceImpl.java:19)
│
│ ──── Java 反射层 ────
│ │ at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
│ │ at jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
│ │ at jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
│ │ at java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)
│ │ at java.lang.reflect.Constructor.newInstance(Constructor.java:481)
│
│ ──── Spring Bean 实例化 ────
│ │ at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:195)
│ │ at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:94)
│ │ at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1331)
│ │ at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)
│ │ at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
│ │ at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
│
│ ──── Bean 获取 & 单例注册 ────
│ │ at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
│ │ at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
│ │ at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
│ │ at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
│
│ ──── 应用上下文启动 ────
│ │ at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975)
│ │ at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:971)
│ │ at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625)
│ │ at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
│
│ ──── Spring Boot 启动入口 ────
│ │ at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
│ │ at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456)
│ │ at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
│ │ at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
└──► at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
那属性填充呢? 我们可以打字段断点来观察属性注入, 但是奇怪的是字段级别的注入,IDEA的字段断点不生效,当我猜测是字段断点对反射不生效的时候,我在set方法上打断点就检测到了:
┌─► ① A01TestServiceImpl.setA02TestService(A01TestServiceImpl.java:21)
│ ──── Java 反射层 ────
│ │ at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
│ │ at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
│ │ at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
│ │ at java.lang.reflect.Method.invoke(Method.java:569)
│
│ ──── Spring - @Autowired 方法注入 ────
│ │ at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:854)
│ │ at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145)
│ │ at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:509)
│
│ ──── Bean 创建流程 ────
│ │ at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1439)
│ │ at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599)
│ │ at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
│ │ at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
│ │ at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
│ │ at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
│ │ at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
│
│ ──── ApplicationContext 启动 ────
│ │ at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975)
│ │ at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:971)
│ │ at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625)
│ │ at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
│
│ ──── Spring Boot 启动入口 ────
│ │ at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
│ │ at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456)
│ │ at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
│ │ at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
└──► at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
这看起来没什么问题,但是我却想起了创建代理对象的链路,但是想了想方法执行完出栈,观察不到代理的链路也在情理之中。但是当时看到这里的时候,我在AbstractAutoProxyCreator的wrapIfNecessary方法中打上断点, 我观测的Bean没有创建代理对象,这让我有点意外, 我观测的Bean代码如下:
@Component
public class A01TestServiceImpl implements A01TestService {
@Autowired
private A02TestService a02TestService;
public A01TestServiceImpl() {
System.out.println();
}
@Async
@Override
public void sayHello02(){
System.out.println("hello world");
}
}
但是如果在sayHello02上打上事务注解如下所示, 这里就会创建代理对象:
@Async
@Transactional
@Override
public void sayHello02(){
System.out.println("hello world");
}
Aware的动机
这增强的时机还不通,一些增强创建的代理的时间更靠前一点。我们接着来验证Aware回调, 那为什么要引入Aware回调呢? 我们首先看Spring留了几个aware回调,然后尝试推导其引入的动机:
- BeanNameAware: void setBeanName(String name);
- ApplicationContextAware:void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
如果你对MyBatis的源码比较熟悉, 就会这两个接口有点印象,MyBatis和Spring整合有一个关键的类就是MapperScannerConfigurer:
BeanNameAware用于获取我们配置的Bean的id的名称, 然后如果我们配置的占位符形式,我们通过名字取拿BeanDefinition来做占位符替换。BeanDefinitionRegistryPostProcessor用于向容器里面注册Bean, 但是有可能这个Bean需要占位符的替换, 占位符的替换是通过BeanDefinition来做的。于是我们就需要通过beanName去拿自身的BeanDefinition。而ApplicationContextWare则可以让我们去解析路径下面的资源,这只是它的能力之一。我们能用它来调度容器的能力。
- EnvironmentAware: void setEnvironment(Environment environment); 获取环境信息。
现在我们自己实现一个Aware接口来看,Spring是怎么调用的Aware接口的:
@Component
public class MyBeanNameAware implements BeanNameAware {
@Override
public void setBeanName(String name) {
System.out.println(name);
}
}
然后打上断点, 观察堆栈。
┌─ Bean 实例化 ───────────────────────────────────────────────
│ setBeanName:14 MyBeanNameAware
│ invokeAwareMethods:1818 调用Aware的接口的方法 support.AbstractAutowireCapableBeanFactory
│ initializeBean:1794 ↑ 调用各种实现了InitializingBean和Aware接口
│ doCreateBean:600 ↑ 创建具体 Bean
│ createBean:522
│ λ doGetBean:337 org.springframework.beans.factory.support.AbstractBeanFactory
│ getSingleton:234 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
│ doGetBean:335
│ getBean:200
└─ IoC 容器启动 ──────────────────────────────────────────────
preInstantiateSingletons:975 org.springframework.beans.factory.support.DefaultListableBeanFactory
finishBeanFactoryInitialization:971 org.springframework.context.support.AbstractApplicationContext
refresh:625 ↑ 完成 BeanFactory 初始化
refresh:146 org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
refresh:754 org.springframework.boot.SpringApplication
refreshContext:456 ↑ Spring Boot 二次封装
run:335 org.springframework.boot.SpringApplication
run:1363
run:1352
main:18 UserInfoApplication
InitializingBean 的时机
思路同样是我们用一个Bean实现InitializingBean,然后在对应的方法里面打上断点,观察方法调用链路即可:
@Component
public class MyInitializingBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("hello world");
}
}
然后观察线程堆栈可以看到,在AbstractAutowireCapableBeanFactory的initializeBean里面获取所有initializeBean的对象,然后触发对应的方法:
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 看这个方法的实现
invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.hasAnyExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
// 验证完毕
((InitializingBean) bean).afterPropertiesSet();
}
if (mbd != null && bean.getClass() != NullBean.class) {
String[] initMethodNames = mbd.getInitMethodNames();
if (initMethodNames != null) {
for (String initMethodName : initMethodNames) {
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.hasAnyExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd, initMethodName);
}
}
}
}
}
容器的关闭阶段
该如何让进程停止运行
容器的创建、运行阶段,我们创造出一些对象,有些对象申请了一些资源,在容器关闭的时候,这些Bean申请的资源要一并被释放掉。那第一个问题,我们该怎么关闭容器? 回忆我的学习过程,最初我是在Servlet语境下编写代码,我们的代码跑在Servlet容器Tomcat里面,事实上应当上关闭容器,进而关闭IOC容器。
但是我想到容器关闭的时候,我想到的代码确是:
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
applicationContext.close();
但注意编译不过去,但是我还是觉得会有一个close方法, 于是我将代码变型得到了:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
applicationContext.close();
上面的代码应该是在看视频里面学到的,我下意识里面也认为容器里面也应该这么关闭,但是我在Spring 官方文档里面看到了下面这段话:
If you use Spring’s IoC container in a non-web application environment (for example, in a rich client desktop environment), register a shutdown hook with the JVM. Doing so ensures a graceful shutdown and calls the relevant destroy methods on your singleton beans so that all resources are released. You must still configure and implement these destroy callbacks correctly.
如果是你在非web应用环境使用IOC容器,你应当给住JVM注册一个关闭钩子。这样可以确保应用在停止时优雅暂停,自动调用所有单例Bean上的销毁方法,释放资源。需要注意的是,相关的销毁仍需你在Bean中正确实现。
To register a shutdown hook, call the registerShutdownHook() method that is declared on the ConfigurableApplicationContext interface, as the following example shows:
需要注册钩子,调用ConfigurableApplicationContext的registerShutdownHook方法即可,代码如下所示:
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(new String []{"beans.xml"});
ctx.registerShutdownHook();
registerShutdownHook的逻辑也比较简单:
public void registerShutdownHook() {
if (this.shutdownHook == null) {
// No shutdown hook registered yet.
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
@Override
public void run() {
if (isStartupShutdownThreadStuck()) {
active.set(false);
return;
}
startupShutdownLock.lock();
try {
doClose();
}
finally {
startupShutdownLock.unlock();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
这里其实就是创建一个线程对象,然后加入到JVM的ShudownHook中,在addShutdownHook我们可以看到,在JVM因为两类事件:
- 程序正常退出——当最后一个非守护线程结束,或显式调用
exit(即System.exit)方法时; - 虚拟机因外部中断而终止,例如用户按下
Ctrl-C,或发生用户注销、系统关机等系统级事件。
而关闭的时候,JVM会以未指定的顺序启动所有已注册的钩子。
web容器里面的关闭
接着说回web环境中的关闭,那么Spring在容器里面,应该是在容器的停止极端,执行回收资源的方法,而我们容器又是Servlet标准的视线,在Servlet标准中有这样一个接口ServletContextListener:
public interface ServletContextListener extends EventListener {
default void contextInitialized(ServletContextEvent sce) {
}
default void contextDestroyed(ServletContextEvent sce) {
}
}
容器在销毁的时候会调用contextDestroyed方法,在这个实现类里面关闭IOC容器即可, 在Spring boot 里面观察这个类的实现类要不少,我们平时停止进程的太粗暴也没办法,直接关停。那该怎么看到是哪个实现类在清理资源呢? 首先我们可以引入依赖,通过接口发起停机请求:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然后通过postman通过ip:端口/actuator/shutdown发起请求,就能让Spring Boot正常停机。于是我在ServletContextListener的几个实现类打了断点,想观测这个停机过程, 这几个类分别是:
- ContextCleanupListener
- ContextLoaderListener
- ServletContextDestroyedListener
这代表我的猜想是错误的,我以前有一些错误认知,在探索的道路中会走弯路,探索的道路不是一帆风顺,重塑知识的结构也在于此,将错误的结构变成正确的结构。
于是我只得另辟蹊径,于是我猜想在Spring 正常停机的过程中会调用线程去做清理动作,所以我想到了另外一个方法,也就是在发出停止信号之后,用IDEA的Thread dump去观测线程的动作:
于是我们就知道了,其实是调用的是ServletWebServerApplicationContext去释放资源,接下来,我们只用关注doClose的实现就可以了:
protected void doClose() {
// Check whether an actual close attempt is necessary...
if (this.active.get() && this.closed.compareAndSet(false, true)) {
if (logger.isDebugEnabled()) {
logger.debug("Closing " + this);
}
try {
// Publish shutdown event. 发布shutdown 事件
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// Stop all Lifecycle beans, to avoid delays during individual destruction.
// 停lifecycleProcessor的onClose事件
if (this.lifecycleProcessor != null) {
try {
this.lifecycleProcessor.onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
}
// Destroy all cached singletons in the context's BeanFactory.
// 销毁所有的单例Bean
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// Let subclasses do some final clean-up if they wish...
onClose();
// Reset common introspection caches to avoid class reference leaks.
resetCommonCaches();
// Reset local application listeners to pre-refresh state.
if (this.earlyApplicationListeners != null) {
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Reset internal delegates.
this.applicationEventMulticaster = null;
this.messageSource = null;
this.lifecycleProcessor = null;
// Switch to inactive.
this.active.set(false);
}
}
我们观测destroyBeans的行为即可, 不要忘记我们看源码是为了验证行为,我们需要验证的时候Spring IOC容器在关闭的时候,会先触发close方法,然后在触发DisposableBean的方法,@PreDestroy标记的方法。我们在DefaultSingletonBeanRegistry的destroySingletons可以看到调用DisposableBean接口中方法的实现:
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();
}
@PreDestroy和DisposableBean的作用类似,@PostConstruct和InitializingBean的作用类似。只不过接口来自于Spring , 注解来自于JSR-250,属于框架中立的,如果你想用依赖注入,但是又不想和Spring耦合在一起,那么使用注解就是一个不错的选择。
一个例子
上面我们讲了一些抽象的理论, 这么说可能有点抽象。我们以RocketMQTemplate为例,RocketMQTemplate就实现了DisposableBean,对应的destroy方法如下:
@Override
public void destroy() {
if (Objects.nonNull(producer)) {
producer.shutdown();
}
if (Objects.nonNull(consumer)) {
consumer.shutdown();
}
}
在consumer的shutdown方法实现里面,我们一路向下跟,可以看到里面确实做了一些清理工作:
//在 RocketMQTemplate的defaultLitePullConsumerImpl可以看到shutdown方法的实现
public synchronized void shutdown() {
switch (this.serviceState) {
case CREATE_JUST:
break;
case RUNNING:
persistConsumerOffset();
this.mQClientFactory.unregisterConsumer(this.defaultLitePullConsumer.getConsumerGroup());
// 清理线程池
scheduledThreadPoolExecutor.shutdown();
scheduledExecutorService.shutdown();
this.mQClientFactory.shutdown();
this.serviceState = ServiceState.SHUTDOWN_ALREADY;
log.info("the consumer [{}] shutdown OK", this.defaultLitePullConsumer.getConsumerGroup());
break;
default:
break;
}
}
总结一下
到现在都连接起来了,发现我有个毛病,就是总希望将我所有的疑问都塞进一片文章来解决,这样高情商的说法,低情商的说法是没有聚焦一个话题,带来的上下文太多。我已经意识到了这个问题,尽量的裁剪篇幅。 在讲到Spring留给我们的扩展点的时候,尽量联系了实例。我们先是宏观上讲IOC的容器上分为四个阶段,
第一个阶段是: 创建阶段收集Bean的元信息,然后进入到BeanFactoryPostProcessor的逻辑中,对Bean进行处理,比如占位符替换之类的。
第二个阶段是:创建Bean阶段,根据BeanDefintion进行注入,注入之后触发InitializingBean, 然后是Aware回调。Aware回调可以让Bean获取到自身的信息,在这个阶段IOC容器可以通过Aware回调,交给这个Bean。一般给其他框架加入IOC容器用。
第三个阶段是:使用阶段,这一阶段一般也就是通过BeanFactory或者ApplicationContext获取容器里面的Bean。
第四个阶段: 释放Bean身上的资源,触发DisposableBean的方法,完成一些资源清理工作。
参考资料
[1] Spring Framework Documentation docs.spring.io/spring-fram…