微信公众号:大黄奔跑
关注我,可了解更多有趣的面试相关问题。
写在之前
上一篇文章介绍了 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。
全部评论
(0) 回帖