Bean的生命周期
微信公众号:大黄奔跑
关注我,可了解更多有趣的面试相关问题。
写在之前
上一篇文章介绍了 Spring 容器中 Bean 的实例化过程大体分为两个阶段:
1、容器启动阶段
2、Bean 实例化阶段
上一篇文章介绍了容器启动的过程及 BeanFactory 如何实例化 Bean 对象,今天继续带领大家领略 Bean 的生命全程细节——Bean 实例化阶段的实现逻辑。
关于本篇的主题面试中也是经常考察的,比如
- Bean生命周期,怎么初始化的
- Bean生命周期【阿里、京东】
- Bean生命周期,哪些地方可以扩展
- Spring Bean生命周期,你在实际中有什么应用【阿里】—> 这里可以说说 InitializingBean的用法
Bean的诞生
之前介绍了 Spring 容器有两种形式 BeanFactory 和 ApplicationContext,两者生成 Bean 的时机是不同的。
对于
BeanFactory来说,对象实例化默认采用延迟初始化。举个例子:当对象A被请求而需 要第一次实例化的时候,如果它所依赖的对象B之前同样没有被实例化,那么容器会先实例化 对象A所依赖的对象。这时容器内部就会首先实例化对象B,以及对象 A依赖的其他还没有被 实例化的对象。简单而言就是依赖传递。
ApplicationContext启动之后会实例化所有的bean定义。ApplicationContext在实现的过程中依然遵循Spring容器实现流程的两个阶段,只不过它 会在启动阶段的活动完成之后,紧接着调用注册到该容器的所有Bean定义的实例化方法getBean()。
无论是使用者手动调用还是自动调用,Bean 都会在 getBean() 方法第一次调用的时候被实例化。Bean 的诞生逻辑如下:
Spring 容器将统一管理所有的 Bean 对象,所有 Bean 拥有统一的生命周期管理,这些被管理的对象完全摆脱了原来那种 “new完后被使用,脱离作用域后即被回收 ” 的命运。

下面我们一起看看 Bean 是如何走过自己愉快的一生。
Bean的生老病死
1. 实例化Bean
容器内部,采用"策略模式"来生成不同类型的 Bean,比如最顶层为一个实例化的策略接口—InstantiationStrategy,该接口是实例化策略 的抽象接口。具体的实例化思路由其子类实现。
其实现主要有两种方式
1、SimpleInstantiationStrategy
2、CglibSubclassingInstantiationStrategy
其类结构和作用如下图:默认情况下,容器内部采用的是 CglibSubclassingInstantiationStrategy 进行实例化工具。
根据容器启动阶段生成的 BeanDefintion 取得实例化信息,结合 CglibSubclassingInstantiationStrategy 以及不同的 bean 定义类型,就可以返回实例化完成的对象实例。这一步并不是直接返回 Bean 的实例化对象,而是以 BeanWrapper 对构造完成的对象实例进行包装,返回相应的 BeanWrapper 实例。"宛如遮着面纱的新娘子,犹抱琵琶半遮面"
到这一步,上图变成如下模样,最后产出的为一个 BeanWrapper 对象。
什么是BeanWrapper呢?
对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或者获取bean的相应属性值。
主要为了第二步给 Bean 设置属性使用。
问题1:这里为啥需要包裹成BeanWrapper呢,而不是直接生成Bean对象呢,毕竟Bean对象也可以设置属性。
使用 BeanWrapper 对 Bean 实例操作很方便,可以免去直接使用 Java 反射 API 操作对象实例的烦琐
2. 检查各种各样的Aware
这一步,Spring 容器会检查当前对象实例是否实现了一系列的以 Aware 命名结尾的接口定义。如果是,则将这些 Aware 接口定义中规定的依赖注入给当前对象实例。
针对不同的容器,有不同的 Aware 方法
BeanFactory
BeanNameAware:该接口将该对象实例的Bean定义对应的BeanName设置到当前对象实例。BeanClassLoaderAware:用于将对应加载当前Bean的Classloader注入当前对象实例。BeanFactoryAware:如果对象声明实现了BeanFactoryAware接口,BeanFactory容器会将自身设置到当前对象实例。当前对象 实例就拥有了一个BeanFactory容器的引用,可以对这个容器内允许访问的对象按照"需要"(比如是单例还是prototype)进行访问。
ApplicationContext
ResourceLoaderAware:将当前ApplicationContext自身设置到对象实例,这样 当前对象实例就拥有了其所在ApplicationContext容器的一个引用。ApplicationEventPublisherAware:ApplicationContext容器如果检测到当前实例 化的对象实例声明了ApplicationEventPublisherAware接口,则会将自身注入当前对象。ApplicationContextAware:如果ApplicationContext容器检测到当前对象实现了ApplicationContextAware接口,则会将自身注入当前对象实例
到这里,其实一个 Bean 该有的属性基本上都加上了,后续只是一些 Bean 初始化之前或者之后的额外操作。
再继续补充上面 Bean 初始化的图:

3. BeanPostProcessor
BeanPostProcessor的概念容易与BeanFactoryPostProcessor的概念混淆。但只要记住BeanPostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段, 这两个概念就比较容易区分了
该接口声明了两个方法postProcessBeforeInitialization、postProcessAfterInitialization。
postProcessBeforeInitialization:该方法主要针对Spring在Bean初始化时调用初始化方法前进行自定义处理。postProcessAfterInitialization:该方法主要针对Spring在Bean初始化时调用初始化方法后进行自定义处理。
主要使用场景:
可以通过 BeanPostProcessor 对当前对象实例做更多 的处理,比如替换当前对象实例或者字节码增强当前对象实例。
目前从资料来看,似乎在生产环境中还没有发现很独特的用处,后续如果发现巧妙使用场景再补充。
4. InitializingBean和init-method
InitializingBean 是容器内部广泛使用的一个对象生命周期标识接口。
该接口只有一个方法,定义如下:
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
} 如果 Bean 对象实现了 InitializingBean,则会调用其 afterPropertiesSet() 方法进一步调整对象实例的状态。
最常见的场景:当需要像容器中加载某项配置文件时,可以实现该接口,观察配置加载情况;或者用于工厂方法的工厂类构建。
在有些情况下,某个业务对象实例化完成后,还不能处于可以使用状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet() 中完成对该业务对象的后续处理
demo 如下:
主要逻辑在于判断配置文件是否存在,如果不存在,及时通过日志感知。
public class ConfigBean implements InitializingBean{
//配置文件
private String configFile;
private String appid;
private String appsecret;
public String getConfigFile() {
return configFile;
}
public void setConfigFile(String configFile) {
this.configFile = configFile;
}
@Override
public void afterPropertiesSet() throws Exception {
if(configFile!=null){
File cf = new File(configFile);
if(cf.exists()){
Properties pro = new Properties();
pro.load(new FileInputStream(cf));
appid = pro.getProperty("wechat.appid");
appsecret = pro.getProperty("wechat.appsecret");
}
}
System.out.println(appid);
System.out.println(appsecret);
}
} 但是该方法,直接需要对应的 Bean 直接实现 InitializingBean 接口,本身对于代码有一定的侵入性。Spring 还提供了另一种方式来指定自定义的对象初始化操作,那就 是在 XML 配置的时候,使用<bean>的 init-method 属性。</bean>
通过 init-method,系统中业务对象的 自定义初始化操作可以以任何方式命名,而不再受制于 InitializingBean 的afterPropertiesSet()。
如果系统开发过程中规定:所有业务对象的自定义初 始化操作都必须以 init() 命名,为了省去挨个 <bean> 的设置 init-method 这样的烦琐,我们还可以通 过最顶层的 <beans> 的 default-init-method 统一指定这一 init()方法名。</beans></bean>
GoodsService.java 表示 商品服务
public class GoodsService {
//乐观锁冲突最大重试次数
private static final int DEFAULT_MAX_RETRIES = 5;
public void initIt() throws Exception {
System.out.println("Init method after properties are set : " + DEFAULT_MAX_RETRIES);
}
} Beans.xml 文件配置
<bean id="goodsService" class="com.jesper.seckill.service.GoodsService"
init-method="initIt">
<description>"用户商品"</description>
</bean> 项目每次启动时,会调用该 initIt() 方法:

5. DisposableBean与destroy-method
与 InitializingBean 和 init-method 用于对象的自定义初始化相对应,DisposableBean 和 destroy-method 为对象提供了执行自定义销毁逻辑的机会。
最常见到的该功能的使用场景就是在 Spring 容器中注册数据库连接池,在系统退出后,连接池应该关闭,以释放相应资源。
不过,这些自定义的对象销毁逻辑,在对象实例初始化完成并注册了相关的回调方法之后,并不会马上执行。
回调方法注册后,返回的对象实例即处于使用状态,只有该对象实例不再被使用的时候, 才会执行相关的自定义销毁逻辑,此时通常也就是 Spring 容器关闭的时候。但Spring容器在关闭之前, 不会聪明到自动调用这些回调方法。所以,需要我们告知容器,在哪个时间点来执行对象的自定义销毁方法。
如下所示:
public class GoodsService implements DisposableBean {
//乐观锁冲突最大重试次数
private static final int DEFAULT_MAX_RETRIES = 5;
public void initIt() throws Exception {
System.out.println("Init method after properties are set : " + DEFAULT_MAX_RETRIES);
}
@Override
public void destroy() throws Exception {
System.out.println("destroy() method is execute");
}
} 启动类文件:

最后可以看到执行了 destroy() 方法,并且容器顺利关闭了。

总结
这里基本基本上介绍完了 Bean 的生命周期全过程,基本上是开篇第一张图的右侧对象实例化部分的介绍。关于 Bean 的生命周期面试中也是常考题目,大家可以结合本篇文章的 Bean 实例化图进行回答。
同时,我们也可以看到 介绍 Bean 实例化时,绕不开两个容器 BeanFactory 和 ApplicationContext 。上一篇文章介绍了 BeanFactory,下一篇继续带大家探讨什么是 ApplicationContext。
#学习路径##Java#
腾讯音乐娱乐集团公司福利 285人发布