• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

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

java 搞代码 4年前 (2022-02-19) 18次浏览 已收录 0个评论
文章目录[隐藏]

起源: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接口,如果是的话,来源gaodaima#com搞(代@码网那么就将其本身注入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开发手册(嵩山版)》最新公布,速速下载!

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


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:关于java:面试官说说-Spring-Bean-的实例化过程面试必问的

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址