首页 > Bean的生命周期
头像
大黄奔跑
编辑于 2021-04-27 10:05
+ 关注

Bean的生命周期

微信公众号:大黄奔跑
关注我,可了解更多有趣的面试相关问题。

写在之前

上一篇文章介绍了 Spring 容器中 Bean 的实例化过程大体分为两个阶段:

1、容器启动阶段

2、Bean 实例化阶段

Bean是如何被实例化的

上一篇文章介绍了容器启动的过程及 BeanFactory 如何实例化 Bean 对象,今天继续带领大家领略 Bean 的生命全程细节——Bean 实例化阶段的实现逻辑。

关于本篇的主题面试中也是经常考察的,比如

  1. Bean生命周期,怎么初始化的
  2. Bean生命周期【阿里、京东】
  3. Bean生命周期,哪些地方可以扩展
  4. Spring Bean生命周期,你在实际中有什么应用【阿里】—> 这里可以说说 InitializingBean的用法

Bean的诞生

之前介绍了 Spring 容器有两种形式 BeanFactoryApplicationContext,两者生成 Bean 的时机是不同的。

  1. 对于 BeanFactory 来说,对象实例化默认采用延迟初始化。

    举个例子:当对象A被请求而需 要第一次实例化的时候,如果它所依赖的对象B之前同样没有被实例化,那么容器会先实例化 对象A所依赖的对象。这时容器内部就会首先实例化对象B,以及对象 A依赖的其他还没有被 实例化的对象。简单而言就是依赖传递

    容器初始化
  2. ApplicationContext 启动之后会实例化所有的 bean 定义。ApplicationContext 在实现的过程中依然遵循 Spring 容器实现流程的两个阶段,只不过它 会在启动阶段的活动完成之后,紧接着调用注册到该容器的所有 Bean 定义的实例化方法 getBean()

无论是使用者手动调用还是自动调用,Bean 都会在 getBean() 方法第一次调用的时候被实例化。Bean 的诞生逻辑如下:

Spring 容器将统一管理所有的 Bean 对象,所有 Bean 拥有统一的生命周期管理,这些被管理的对象完全摆脱了原来那种 “new完后被使用,脱离作用域后即被回收 ” 的命运。

Bean实例化过程

下面我们一起看看 Bean 是如何走过自己愉快的一生。

Bean的生老病死

1. 实例化Bean

容器内部,采用"策略模式"来生成不同类型的 Bean,比如最顶层为一个实例化的策略接口—InstantiationStrategy,该接口是实例化策略 的抽象接口。具体的实例化思路由其子类实现。

其实现主要有两种方式

1、SimpleInstantiationStrategy

2、CglibSubclassingInstantiationStrategy

其类结构和作用如下图:默认情况下,容器内部采用的是 CglibSubclassingInstantiationStrategy 进行实例化工具。

实例化策略及作用

根据容器启动阶段生成的 BeanDefintion 取得实例化信息,结合 CglibSubclassingInstantiationStrategy 以及不同的 bean 定义类型,就可以返回实例化完成的对象实例。这一步并不是直接返回 Bean 的实例化对象,而是以 BeanWrapper 对构造完成的对象实例进行包装,返回相应的 BeanWrapper 实例。"宛如遮着面纱的新娘子,犹抱琵琶半遮面"

到这一步,上图变成如下模样,最后产出的为一个 BeanWrapper 对象。

实例化Bean对象第一步

什么是BeanWrapper呢?

对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或者获取bean的相应属性值。

主要为了第二步给 Bean 设置属性使用。

问题1:这里为啥需要包裹成BeanWrapper呢,而不是直接生成Bean对象呢,毕竟Bean对象也可以设置属性。

使用 BeanWrapper 对 Bean 实例操作很方便,可以免去直接使用 Java 反射 API 操作对象实例的烦琐

2. 检查各种各样的Aware

这一步,Spring 容器会检查当前对象实例是否实现了一系列的以 Aware 命名结尾的接口定义。如果是,则将这些 Aware 接口定义中规定的依赖注入给当前对象实例。

针对不同的容器,有不同的 Aware 方法

BeanFactory

  1. BeanNameAware:该接口将该对象实例的 Bean 定义对应的 BeanName 设置到当前对象实例。
  2. BeanClassLoaderAware:用于将对应加载当前BeanClassloader 注入当前对象实例。
  3. BeanFactoryAware:如果对象声明实现了 BeanFactoryAware 接口,BeanFactory 容器会将自身设置到当前对象实例。当前对象 实例就拥有了一个 BeanFactory 容器的引用,可以对这个容器内允许访问的对象按照"需要"(比如是单例还是 prototype )进行访问。

ApplicationContext

  1. ResourceLoaderAware:将当前 ApplicationContext 自身设置到对象实例,这样 当前对象实例就拥有了其所在ApplicationContext 容器的一个引用。

  2. ApplicationEventPublisherAwareApplicationContext 容器如果检测到当前实例 化的对象实例声明了ApplicationEventPublisherAware 接口,则会将自身注入当前对象。

  3. ApplicationContextAware:如果 ApplicationContext 容器检测到当前对象实现了 ApplicationContextAware 接口,则会将自身注入当前对象实例

到这里,其实一个 Bean 该有的属性基本上都加上了,后续只是一些 Bean 初始化之前或者之后的额外操作。

再继续补充上面 Bean 初始化的图:

Bean初始化过程

3. BeanPostProcessor

BeanPostProcessor的概念容易与BeanFactoryPostProcessor的概念混淆。但只要记住BeanPostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段, 这两个概念就比较容易区分了

该接口声明了两个方法postProcessBeforeInitializationpostProcessAfterInitialization

  • postProcessBeforeInitialization:该方法主要针对 SpringBean 初始化时调用初始化方法进行自定义处理。

  • postProcessAfterInitialization:该方法主要针对 SpringBean 初始化时调用初始化方法进行自定义处理。

BeanPostProcessor作用

主要使用场景:

可以通过 BeanPostProcessor 对当前对象实例做更多 的处理,比如替换当前对象实例或者字节码增强当前对象实例

目前从资料来看,似乎在生产环境中还没有发现很独特的用处,后续如果发现巧妙使用场景再补充。

4. InitializingBeaninit-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,系统中业务对象的 自定义初始化操作可以以任何方式命名,而不再受制于 InitializingBeanafterPropertiesSet()

如果系统开发过程中规定:所有业务对象的自定义初 始化操作都必须以 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

InitializingBeaninit-method 用于对象的自定义初始化相对应,DisposableBeandestroy-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");
    }
}

启动类文件:

image-20210427090938508

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

部分启动日志

总结

这里基本基本上介绍完了 Bean 的生命周期全过程,基本上是开篇第一张图的右侧对象实例化部分的介绍。关于 Bean 的生命周期面试中也是常考题目,大家可以结合本篇文章的 Bean 实例化图进行回答。

同时,我们也可以看到 介绍 Bean 实例化时,绕不开两个容器 BeanFactory 和 ApplicationContext 。上一篇文章介绍了 BeanFactory,下一篇继续带大家探讨什么是 ApplicationContext。

全部评论

(0) 回帖
加载中...
话题 回帖

推荐话题

相关热帖

近期热帖

近期精华帖

热门推荐