Spring—普通Bean的生命周期

1.前言概述

Spring Bean的生命周期是我们老生常谈的话题,不仅是高频面试题,也是我们对Spring的理解的入门。在Spring中,万物皆为Bean,统统由Spring容器进行管理,在需要的时候即可提供。我想我们都知道Spring中的两大亮点:

  • AOP 面向切面编程
  • IOC 控制反转

这篇文章主要围绕Bean对象的IOC来聊聊, 其实无论是IOC还是DI(依赖注入),这两种表达的都是一种概念,只是角度问题,就和我们生活中的坐北朝南一样,其实坐北也就是背靠着北实际上就是朝南。IOC控制反转,将对象的管理权交给了容器,是从容器角度触发;依赖注入是将容器中的对象注入到对应Bean对象,是从对象的角度出发的。基于此,我们来看看Spring是如何把对象加载完成放入容器,又是如何使用容器的中的对象注入到对应Bean中。

说到Bean,那必须得了解获取Bean的方式。获取Spring Bean的最顶层接口是BeanFactory, 后续又新增了ApplicationContext。后者继承了前者,说明后者提供了更强大的功能。事实上也确实如此,后者提供了国际化、事件机制、环境参数获取等,由于其强大的能力,因此占用的内存比BeanFactory多。打个比方,如果BeanFactory是一间茅草屋的话,ApplicationContext就是一幢别墅。Bean工厂可以为容器生产Bean,那生成Bean的原料一定是BeanDefinition,基于该原料可以生成对应的Bean。

2.单个Bean的生命周期

众所周知,任何一个对象都不能缺少实例化和属性填充,Spring Bean也不例外,Spring Bean还需要进行初始化的过程。Spring多出初始化过程是因为在Bean属性填充完毕后,需要提供很多对该bean后期的扩展埋点处理,自定义注解解析的过程就是一个典型的例子。那接下来就看看整个Bean的创建流程:

Spring—普通Bean的生命周期
上述调用的方法是org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)方法,该方法的lambda回调会触发创建bean的过程,在触发创建Bean的回调方法之前,都是会去第一级缓存中去获取,获取不到的时候才会去走创建bean的流程。在该方法内部有个方法beforeSingletonCreation会将当前创建的bean添加到singletonsCurrentlyInCreation集合,这是后期为了判断这个bean是否正在创建阶段, 另外如果bean出现循环依赖时可以用于判断抛出异常跳出循环。 因为前期add过一次,然后在循环依赖没有开启的情况下只使用一级缓存,该缓存中没有找到然后又要去重新创建,这时在add的时候会返回fasle因此会抛出异常。

实例化前

实例化前后处理器可用于创建代理对象,对于普通bean而言的话,这里是返回null的, 然后执行doCreateBean方法执行真正的创建Bean的方法。

Spring—普通Bean的生命周期

实例化

上述即可实现实例化Bean的流程,当然内部存在构造器推断的逻辑,也就是当Bean中存在多个构造器时,Spring得决定选择那一个构造器进行实例化对象。先检查是否有@Autowired注解的构造函数 ,如果没有检查高优先级的构造函数 ,最后使用无参的构造函数。

Spring—普通Bean的生命周期

实例化后

调用postProcessMergedBeanDefinition缓存Bean中的注解的元数据,缓存注解的元数据是为后期服务其注解包括@Autowired,@Value,@PreDestory,@PostConstruct等注解。前两者是通过AutowiredAnnotationBeanPostProcessor实现的,后两者是通过CommonAnnotationBeanPostProcessor实现的。

Spring—普通Bean的生命周期

addSingletonFactory方法是将后面的lambda表达式添加到第三级缓存,添加到第三级缓存只是在循环依赖开关开启的情况下执行,并且不论Bean是否发生循环依赖均会使用第三级缓存。

Spring—普通Bean的生命周期

属性填充populateBean方法主要做了两件事:

  1. 执行实例化后的后处理器,如果该后处理器返回为false便不会执行后续逻辑(例如属性填充),该后处理器可以由开发者实现该接口中的方法;
  2. 使用postProcessProperties方法给加上@Autowired和@Value填充Bean的属性;
Spring—普通Bean的生命周期
Spring—普通Bean的生命周期
Spring—普通Bean的生命周期

初始化Bean

initializeBean方法主要做了四件事,我在上述图中进行了标注,接下来对这四步详细描述一下:

1. 执行实现Aware相关接口的方法

该接口下的实现有三个,分别是BeanNameAware、BeanClassLoaderAware、BeanFactoryAware。这三者的作用我个人理解是Spring为了开发中扩展埋点所预留的,开发中很少使用。

2. 执行Bean初始化的后处理器

该后处理器提供了postProcessBeforeInitialization方法用于扩展埋点,在初始化前执行的方法,就是在这个过程帮助我们执行了了PostConstruct注解的方法。

3. 执行Bean初始化方法

该过程分成两类:一类是执行实现InitializingBean接口的afterPropertiesSet方法;一类是自定义的初始化方法。如果两者都存在是都会执行的,前者先执行后者后执行,例子如下:

public class BeanD implements InitializingBean {
    public BeanD(){
        log.info("BeanD + Constructor");
    }
    //该方法是属于初始化第一类,如果想在SpringBoot启动时执行该方法,在该类上加一个成为Bean的注解就好了,例如:@Service
    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("BeanD + afterPropertiesSet");
    }

    //该方法属于第二类,如果想要执行时,在类上加注解是解决不了的
    public void init() {
        log.info("BeanD + init");
    }
}
//第二类自定义初始化方法生效方式必须使用Bean注解的方式
@Configuration
@Slf4j
public class BeanConfiguration {

    //在Bean注解的initMethod参数加上自定义的初始化方法,该方法名就是BeanD中自定义的初始化方法名
    @Bean(initMethod = "init")
    public BeanD beanD(){
        return new BeanD();
    }
}

4. 执行Bean初始化的后处理器

对于执行初始化后的后处理器方法,我相信写过自定义注解的都不会陌生。通常情况下,需要对自定义注解的解析逻辑就是在这里完成。

Spring—普通Bean的生命周期
Spring—普通Bean的生命周期

创建完成后一定会调用afterSingletonCreation移除singletonsCurrentlyInCreation集合中的bean,表明这个bean已经创建完成了,不处于创建阶段。

将完整的Bean添加到第一级缓存并对第二三级缓存移除。

3.嵌入Bean的生命周期

嵌入Bean是指BeanA中注入了BeanB时,整个Bean的加载过程。Bean在加载过程不是同时去加载完成的,也就是Bean不会一批完成某个阶段,比如一批Bean进行初始化,一批bean进行实例化的。Bean加载是加载完成一个再去加载另一个,当BeanA中需要用到BeanB,这时会先放下BeanA加载去加载BeanB,等BeanB加载完成后再去完成BeanA剩下的加载步骤。整个流程如下:

Spring—普通Bean的生命周期

上述的流程图是我在网上看到的,看着比较好引用过来的。

4.普通Bean循环依赖

三级缓存:

  • singletonObjects:第一级缓存,也是spring的单例池;
  • earlySingletonObjects:第二级缓存,存放半成品Bean对象,也就是没有完成整个Bean加载过程的Bean对象。比如只填充一部分属性;
  • singletonFactories:第三级缓存, 存放lambda的表达式,可用于生成代理对象

SpringBoot在2.6版本以后默认关闭了循环依赖,如果要开启循环依赖需要在配置文件中进行配置:spring.main.allow-circular-references=true

关闭循环依赖:Bean在加载的过程中只会添加到第一级缓存,剩下二级缓存没有利用到。当应用中出现循环依赖时会直接报错,分析一下循环依赖关闭情况下报错的原因:主要是这个集合singletonsCurrentlyInCreation

beanA在创建前会往singletonsCurrentlyInCreation集合中添加,表明beanA正在创建中,此时注入属性的beanB时发现beanB不存在,那需要去加载beanB,加载beanB的时候也会往上述的集合中添加beanB,在注入属性时,先在第一级缓存中没有找到(关闭情况下只利用了一级缓存,beanA没有加载完成,所以一级缓存肯定没有),然后又得去创建,在创建的过程中又得往singletonsCurrentlyInCreation集合中添加beanA的名字,这时由于beanA之前添加过一次所以会添加失败返回false,因此会抛出异常。

Note: Bean在创建前总是先去获取三级缓存中获取Bean,当不存在时才去创建。此处我理解当关闭循环依赖的时候不需要三级缓存中获取,只需要在第一级缓存中获取即可。

Spring—普通Bean的生命周期开启循环依赖:Bean在加载的过程中即使没有发生循环依赖也会加入到第三级缓存,因为既然支持了,如果不加入第三级缓存到时候发生循环依赖了就没办法解决了,所以上来就会对Bean添加到第三级缓存。

开启循环依赖解决问题最重要的是第三级缓存,因为当beanA在创建过程中会添加到第三级缓存中,beanB中需要注入beanA属性时,此刻即出现了循环依赖,这时在获取beanA时不会获取不到,因为开启循环依赖后会往第三级缓存中添加,取的时候也会去第三级缓存中取,对取出来的lambda表达式执行获得一个半成品的beanA注入beanB中,重而打破循环。

在发生循环依赖的情况下,最后一个打破循环的beanB可以完成创建,但是该beanB中的属性全部被填充并且beanB走完了整个Bean的加载过程,但是会存在属性beanA的去除beanB以外的其他属性为null的情况,这个beanA就是所谓的半成品。

Spring—普通Bean的生命周期

5.总结

最后,为了方便大家理解,我给上述的整个过程整理成了一张流程图,熟悉整个Bean加载的流程。下图所示的是Bean的整个创建流程,紫色的表示是整个容器级别的,也就是会对除了自己以外的所有Bean都会执行。绿色表示的是Bean级别的,只会作用于当前Bean。

Spring—普通Bean的生命周期


原文始发于微信公众号(处女座码农):Spring—普通Bean的生命周期

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/251602.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!