乐趣区

关于java:面试官说说-Spring-Bean-的实例化过程面试必问的

起源:juejin.cn/post/6929672218322731022

对于写 Java 的程序员来说,Spring 曾经成为了目前最风行的第三方开源框架之一,在咱们充沛享受 Spring IOC 容器带来的红利的同时,咱们也应该考虑一下 Spring 这个大工厂是如何将一个个的 Bean 生产进去的,本期咱们就一起来讨论一下 Spring 中 Bean 的实例化过程。

这里咱们并不会具体的剖析源代码,只是给出 Spring 在实现哪些工作的时候应用到了什么类,这些类具体的职责都是什么,如果咱们要弄清楚 Spring Bean 实例化的底细与详细信息,那么能够看哪些源代码? 至于具体的具体的代码信息,大家能够查看 Spring 相干类的代码。

两个阶段

这里首先申明一下,Spring 将治理的一个个的依赖对象称之为 Bean, 这从 xml 配置文件中也能够看出。

Spring IOC 容器就如同一个生产产品的流水线上的机器,Spring 创立进去的 Bean 就如同是流水线的起点生产进去的一个个精美绝伦的产品。既然是机器,总要先启动,Spring 也不例外。因而 Bean 的毕生从总体上来说能够分为两个阶段:

  • 容器启动阶段
  • Bean 实例化阶段

容器的启动阶段做了很多的预热工作,为前面 Bean 的实例化做好了充沛的筹备,咱们首先看一下容器的启动阶段都做了哪些预热工作。

容器启动阶段

1、配置元信息

咱们说 Spring IOC 容器将对象实例的创立与对象实例的应用拆散,咱们的业务中须要依赖哪个对象不再依附咱们本人手动创立,只有向 Spring 要,Spring 就会以注入的形式交给咱们须要的依赖对象。

然而,你不干,我不干,总要有人干,既然咱们将对象创立的工作交给了 Spring,那么 Spring 就须要晓得创立一个对象所须要的一些必要的信息。而这些必要的信息能够是 Spring 过来反对最欠缺的 xml 配置文件,或者是其余模式的例如 properties 的磁盘文件,也能够是当初支流的注解,甚至是间接的代码硬编码。总之,这些创建对象所须要的必要信息称为配置元信息。

<bean id="role" class="com.wbg.springxmlbean.entity.Role">
    <!-- property 元素是定义类的属性,name 属性定义的是属性名称 value 是值
    相当于:Role role=new Role();
    role.setId(1);
    role.setRoleName("高级工程师");
    role.setNote("重要人员");-->
    <property name="id" value="1"/>
    <property name="roleName" value="高级工程师"/>
    <property name="note" value="重要人员"/>
</bean>

2、BeanDefination

咱们大家都晓得,在 Java 世界中,万物皆对象,散落于程序代码各处的注解以及保留在磁盘上的 xml 或者其余文件等等配置元信息,在内存中总要以一种对象的模式示意,就好比咱们活生生的人对应到 Java 世界中就是一个 Person 类。

而 Spring 抉择在内存中示意这些配置元信息的形式就是 BeanDefination,这里咱们不会去剖析 BeanDefination 的代码,感兴趣的能够去看相干源码,这里咱们只是须要晓得配置元信息被加载到内存之后是以 BeanDefination 的形存在的即可。

3、BeanDefinationReader

大家必定很好奇,咱们是看得懂 Spring 中 xml 配置文件中一个个的 Bean 定义,然而 Spring 是如何看懂这些配置元信息的呢?这个就要靠咱们的 BeanDefinationReader 了。

不同的 BeanDefinationReader 就像葫芦兄弟一样,各自领有各自的本事。如果咱们要读取 xml 配置元信息,那么能够应用 XmlBeanDefinationReader。如果咱们要读取 properties 配置文件,那么能够应用 PropertiesBeanDefinitionReader 加载。

而如果咱们要读取注解配置元信息,那么能够应用 AnnotatedBeanDefinitionReader 加载。咱们也能够很不便的自定义 BeanDefinationReader 来本人管制配置元信息的加载。例如咱们的配置元信息存在于三界之外,那么咱们能够自定义 From 天界之外 BeanDefinationReader。

总的来说,BeanDefinationReader 的作用就是加载配置元信息,并将其转化为内存模式的 BeanDefination,存在某一个中央,至于这个中央在哪里,不要焦急,接着往下看!

4、BeanDefinationRegistry

执行到这里,总算不遗余力的将存在于各处的配置元信息加载到内存,并转化为 BeanDefination 的模式,这样咱们须要创立某一个对象实例的时候,找到相应的 BeanDefination 而后创建对象即可。那么咱们须要某一个对象的时候,去哪里找到对应的 BeanDefination 呢?

这种通过 Bean 定义的 id 找到对象的 BeanDefination 的对应关系或者说映射关系又是如何保留的呢?这就引出了 BeanDefinationRegistry 了。

Spring 通过 BeanDefinationReader 将配置元信息加载到内存生成相应的 BeanDefination 之后,就将其注册到 BeanDefinationRegistry 中,BeanDefinationRegistry 就是一个寄存 BeanDefination 的大篮子,它也是一种键值对的模式,通过特定的 Bean 定义的 id,映射到相应的 BeanDefination。

5、BeanFactoryPostProcessor

BeanFactoryPostProcessor 是容器启动阶段 Spring 提供的一个扩大点,次要负责对注册到 BeanDefinationRegistry 中的一个个的 BeanDefination 进行肯定水平上的批改与替换。

例如咱们的配置元信息中有些可能会批改的配置信息散落到各处,不够灵便,批改相应配置的时候比拟麻烦,这时咱们能够应用占位符的形式来配置。例如配置 Jdbc 的 DataSource 连贯的时候能够这样配置:

<bean id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="maxIdle" value="${jdbc.maxIdle}"></property>
    <property name="maxActive" value="${jdbc.maxActive}"></property>
    <property name="maxWait" value="${jdbc.maxWait}"></property>
    <property name="minIdle" value="${jdbc.minIdle}"></property>

    <property name="driverClassName"
        value="${jdbc.driverClassName}">
    </property>
    <property name="url" value="${jdbc.url}"></property>

    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

BeanFactoryPostProcessor 就会对注册到 BeanDefinationRegistry 中的 BeanDefination 做最初的批改,替换 $ 占位符为配置文件中的实在的数据。

至此,整个容器启动阶段就算实现了,容器的启动阶段的最终产物就是注册到 BeanDefinationRegistry 中的一个个 BeanDefination 了,这就是 Spring 为 Bean 实例化所做的预热的工作。让咱们再通过一张图的模式回顾一下容器启动阶段都是搞了什么事吧。

Bean 实例化阶段

须要指出,容器启动阶段与 Bean 实例化阶段存在多少时间差,Spring 把这个决定权交给了咱们程序员(是不是霎时开心了一点点!)。

如果咱们抉择懒加载的形式,那么直到咱们伸手向 Spring 要依赖对象实例之前,其都是以 BeanDefinationRegistry 中的一个个的 BeanDefination 的模式存在,也就是 Spring 只有在咱们须要依赖对象的时候才开启相应对象的实例化阶段。

而如果咱们不是抉择懒加载的形式,容器启动阶段实现之后,将立刻启动 Bean 实例化阶段,通过隐式的调用所有依赖对象的 getBean 办法来实例化所有配置的 Bean 并保存起来。

接下来咱们就聊一聊 Bean 实例化过程的那些事儿~

1、对象创立策略

到了这个时候,Spring 就开始真刀真枪的干了,对象的创立采纳了策略模式,借助咱们后面 BeanDefinationRegistry 中的 BeanDefination, 咱们能够应用反射的形式创建对象,也能够应用 CGlib 字节码生成创建对象。

同时咱们能够灵便的配置来通知 Spring 采纳什么样的策略创立指定的依赖对象。Spring 中 Bean 的创立是策略设计模式的经典利用。这个时候,内存中应该曾经有一个咱们想要的具体的依赖对象的实例了,然而故事的倒退还没有咱们设想中的那么简略。

对于策略模式有不理解的能够查阅相干书籍,或者网上相干材料,这是设计模式相干的内容,本文次要关注 Bean 实例化的整体流程,设计模式相干常识不在探讨。

2、BeanWrapper——对象的外衣

Spring 中的 Bean 并不是以一个个的原本模样存在的,因为 Spring IOC 容器中要治理多种类型的对象,因而为了对立对不同类型对象的拜访,Spring 给所有创立的 Bean 实例穿上了一层外套,这个外套就是 BeanWrapper(对于 BeanWrapper 的具体内容感兴趣的请查阅相干源码)。

BeanWrapper 实际上是对反射相干 API 的简略封装,使得下层应用反射实现相干的业务逻辑大大的简化,咱们要获取某个对象的属性,调用某个对象的办法,当初不须要在写繁冗的反射 API 了以及解决一堆麻烦的异样,间接通过 BeanWrapper 就能够实现相干操作,几乎不要太爽了。

3、设置对象属性

上一步包裹在 BeanWrapper 中的对象还是一个少不经事的孩子,须要为其设置属性以及依赖对象。

对于根本类型的属性,如果配置元信息中有配置,那么将间接应用配置元信息中的设置值赋值即可,即便根本类型的属性没有设置值,那么得益于 JVM 对象实例化过程,属性仍然能够被赋予默认的初始化零值。

对于援用类型的属性,Spring 会将所有曾经创立好的对象放入一个 Map 构造中,此时 Spring 会查看所依赖的对象是否曾经被纳入容器的治理范畴之内,也就是 Map 中是否曾经有对应对象的实例了。如果有,那么间接注入,如果没有, 那么 Spring 会临时放下该对象的实例化过程,转而先去实例化依赖对象,再回过头来实现该对象的实例化过程。

这里有一个 Spring 中的经典问题,那就是 Spring 是如何解决循环依赖的?

这里简略提一下,Spring 是通过三级缓存解决循环依赖,并且只能解决 Setter 注入的循环依赖,请大家思考一下如何解决?为何只能是 Setter 注入?具体内容能够查阅相干博客,文档,书籍。

4、查看 Aware 相干接口

咱们晓得,咱们如果想要依赖 Spring 中的相干对象,应用 Spring 的相干 API, 那么能够实现相应的 Aware 接口,Spring IOC 容器就会为咱们主动注入相干依赖对象实例。

Spring IOC 容器大体能够分为两种,BeanFactory 提供 IOC 思维所构想所有的性能,同时也融入 AOP 等相干功能模块,能够说 BeanFactory 是 Spring 提供的一个根本的 IOC 容器。ApplicationContext 构建于 BeanFactory 之上,同时提供了诸如容器内的工夫公布、对立的资源加载策略、国际化的反对等性能,是 Spring 提供的更为高级的 IOC 容器。

讲了这么多,其实就是想表白对于 BeanFactory 来说,这一步的实现是先查看相干的 Aware 接口,而后去 Spring 的对象池 (也就是容器,也就是那个 Map 构造) 中去查找相干的实例(例如对于 ApplicationContextAware 接口,就去找 ApplicationContext 实例),也就是说咱们必须要在配置文件中或者应用注解的形式,将相干实例注册容器中,BeanFactory 才能够为咱们主动注入。

而对于 ApplicationContext,因为其自身继承了一系列的相干接口,所以当检测到 Aware 相干接口,须要相干依赖对象的时候,ApplicationContext 齐全能够将本身注入到其中,ApplicationContext 实现这一步是通过上面要讲到的东东——BeanPostProcessor。

例如 ApplicationContext 继承自 ResourceLoader 和 MessageSource,那么当咱们实现 ResourceLoaderAware 和 MessageSourceAware 相干接口时,就将其本身注入到业务对象中即可。

5、BeanPostProcessor 前置解决

唉?方才那个是什么 Processor 来?置信刚看这两个货色的人必定有点晕乎了,我当初也是,不过其实也好辨别,只有记住 BeanFactoryPostProcessor 存在于容器启动阶段而 BeanPostProcessor 存在于对象实例化阶段,BeanFactoryPostProcessor 关注对象被创立之前 那些配置的修修改改,缝缝补补,而 BeanPostProcessor 阶段关注对象曾经被创立之后 的性能加强,替换等操作,这样就很容易辨别了。

BeanPostProcessor 与 BeanFactoryPostProcessor 都是 Spring 在 Bean 生产过程中强有力的扩大点。如果你还对它感到很生疏,那么你必定晓得 Spring 中驰名的 AOP(面向切面编程),其实就是依赖 BeanPostProcessor 对 Bean 对象性能加强的。

BeanPostProcessor 前置解决就是在要生产的 Bean 实例放到容器之前,容许咱们程序员对 Bean 实例进行肯定水平的批改,替换等操作。

后面讲到的 ApplicationContext 对于 Aware 接口的查看与主动注入就是通过 BeanPostProcessor 实现的,在这一步 Spring 将查看 Bean 中是否实现了相干的 Aware 接口,如果是的话,那么就将其本身注入 Bean 中即可。Spring 中 AOP 就是在这一步实现的移花接木,产生对于原生对象的代理对象,而后将对源对象上的办法调用,转而应用代理对象的雷同办法调用实现的。

6、自定义初始化逻辑

在所有的筹备工作实现之后,如果咱们的 Bean 还有肯定的初始化逻辑,那么 Spring 将容许咱们通过两种形式配置咱们的初始化逻辑:

  • InitializingBean
  • 配置 init-method 参数

个别通过配置 init-method 办法比拟灵便。

7、BeanPostProcess 后置解决

与前置解决相似,这里是在 Bean 自定义逻辑也执行实现之后,Spring 又留给咱们的最初一个扩大点。咱们能够在这里在做一些咱们想要的扩大。

8、自定义销毁逻辑

这一步对应自定义初始化逻辑,同样有两种形式:

  • 实现 DisposableBean 接口
  • 配置 destory-method 参数。

这里一个比拟典型的利用就是配置 dataSource 的时候 destory-method 为数据库连贯的 close()办法。

9、应用

通过了以上道道工序,咱们终于能够享受 Spring 为咱们带来的便捷了,这个时候咱们像看待平时的对象一样看待 Spring 为咱们产生的 Bean 实例,如果你感觉还不错的话,入手试一下吧!

10、调用回调销毁接口

Spring 的 Bean 在为咱们服务完之后,马上就要沦亡了(通常是在容器敞开的时候),别忘了咱们的自定义销毁逻辑,这时候 Spring 将以回调的形式调用咱们自定义的销毁逻辑,而后 Bean 就这样走完了光彩的毕生!

咱们再通过一张图来一起看一看 Bean 实例化阶段的执行程序是如何的?

须要指出,容器启动阶段与 Bean 实例化阶段之间的桥梁就是咱们能够抉择自定义配置的提早加载策略,如果咱们配置了 Bean 的提早加载策略,那么只有咱们在实在的应用依赖对象的时候,Spring 才会开始 Bean 的实例化阶段。

而如果咱们没有开启 Bean 的提早加载,那么在容器启动阶段之后,就会紧接着进入 Bean 实例化阶段,通过隐式的调用 getBean 办法,来实例化相干 Bean。

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)

2. 劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.20w 程序员红包封面,快快支付。。。

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

退出移动版