Spring Bean的实例化过程

news2025/1/13 13:31:05

一、前言

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

1.1 Spring Bean 生命周期步骤伪代码

1、类
2、推断选择构造方法(默认调用类无参构造方法)
3、普通对象(new出来)
4、为对象的属性(加了注解的,如@Autowired)进行依赖注入
5、初始化前:判断方法上是否加了@PostConstruct注解
6、初始化:判断是否实现了InitializingBean接口,反射调用afterPropertiesSet方法
7、初始化后:进行AOP
8、代理对象
9、放入单例池Map
10、成为Bean

1.1.1 Spring调用类的构造方法

Spring调用类的构造方法有以下情况:

  • Spring默认会调用类的无参构造方法创建类。

  • 加了仅有的一个有参构造方法(无参构造方法就没有了),Spring会调用该有参构造方法创建类。

  • 加了两个以上的有参构造方法,Spring报错,因为Spring也不清楚要调用哪个构造方法来创建对象。

  • 加了两个以上的有参构造方法,并在一个指定的构造方法上加了@Autowired注解,Spring则调用该构造方法来创建对象。

二、两个阶段

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

Spring IOC容器就好像一个生产产品的流水线上的机器,Spring创建出来的Bean就好像是流水线的终点生产出来的一个个精美绝伦的产品。既然是机器,总要先启动,Spring也不例外。因此Bean的一生从总体上来说可以分为两个阶段:

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

 

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

2.1 容器启动阶段
2.1.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.1.2 BeanDefination

我们大家都知道,在Java世界中,万物皆对象,散落于程序代码各处的注解以及保存在磁盘上的xml或者其他文件等等配置元信息,在内存中总要以一种对象的形式表示,就好比我们活生生的人对应到Java世界中就是一个Person类,而Spring选择在内存中表示这些配置元信息的方式就是BeanDefination,这里我们只是需要知道配置元信息被加载到内存之后是以BeanDefination的形存在的即可。

2.1.3 BeanDefinationReader

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

不同的BeanDefinationReader就像葫芦兄弟一样,各自拥有各自的本领。如果我们要读取xml配置元信息,那么可以使用XmlBeanDefinationReader。如果我们要读取properties配置文件,那么可以使用PropertiesBeanDefinitionReader加载。而如果我们要读取注解配置元信息,那么可以使用 AnnotatedBeanDefinitionReader加载。我们也可以很方便的自定义BeanDefinationReader来自己控制配置元信息的加载。例如我们的配置元信息存在于三界之外,那么我们可以自定义From天界之外BeanDefinationReader。

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

2.1.4 BeanDefinationRegistry

执行到这里,总算不遗余力的将存在于各处的配置元信息加载到内存,并转化为BeanDefination的形式,这样我们需要创建某一个对象实例的时候,找到相应的BeanDefination然后创建对象即可。那么我们需要某一个对象的时候,去哪里找到对应的BeanDefination呢?这种通过Bean定义的id找到对象的BeanDefination的对应关系或者说映射关系又是如何保存的呢?这就引出了BeanDefinationRegistry了。

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

2.1.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实例化所做的预热的工作。

2.2 Bean实例化阶段

需要指出,容器启动阶段与Bean实例化阶段存在多少时间差,Spring把这个决定权交给了我们程序员。如果我们选择懒加载的方式,那么直到我们伸手向Spring要依赖对象实例之前,其都是以BeanDefinationRegistry中的一个个的BeanDefination的形式存在,也就是Spring只有在我们需要依赖对象的时候才开启相应对象的实例化阶段。而如果我们不是选择懒加载的方式,容器启动阶段完成之后,将立即启动Bean实例化阶段,通过隐式的调用所有依赖对象的getBean方法来实例化所有配置的Bean并保存起来。

2.2.1 对象创建策略

到了这个时候,Spring就开始真刀真枪的干了,对象的创建采用了策略模式,借助我们前面BeanDefinationRegistry中的BeanDefination,我们可以使用反射的方式创建对象,也可以使用CGlib字节码生成创建对象。同时我们可以灵活的配置来告诉Spring采用什么样的策略创建指定的依赖对象。Spring中Bean的创建是策略设计模式的经典应用。这个时候,内存中应该已经有一个我们想要的具体的依赖对象的实例了,但是故事的发展还没有我们想象中的那么简单。

2.2.2 BeanWrapper对象的外衣

Spring中的Bean并不是以一个个的本来模样存在的,由于Spring IOC容器中要管理多种类型的对象,因此为了统一对不同类型对象的访问,Spring给所有创建的Bean实例穿上了一层外套,这个外套就是BeanWrapper。BeanWrapper实际上是对反射相关API的简单封装,使得上层使用反射完成相关的业务逻辑大大的简化,我们要获取某个对象的属性,调用某个对象的方法,现在不需要在写繁杂的反射API了以及处理一堆麻烦的异常,直接通过BeanWrapper就可以完成相关操作,简直不要太爽了。

2.2.3 设置对象属性

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

  • 对于基本类型的属性,如果配置元信息中有配置,那么将直接使用配置元信息中的设置值赋值即可,即使基本类型的属性没有设置值,那么得益于JVM对象实例化过程,属性依然可以被赋予默认的初始化零值。

  • 对于引用类型的属性,Spring会将所有已经创建好的对象放入一个Map结构中,此时Spring会检查所依赖的对象是否已经被纳入容器的管理范围之内,也就是Map中是否已经有对应对象的实例了。如果有,那么直接注入,如果没有,那么Spring会暂时放下该对象的实例化过程,转而先去实例化依赖对象,再回过头来完成该对象的实例化过程。

这里有一个Spring中的经典问题,那就是Spring是如何解决循环依赖的?这里简单提一下,Spring是通过三级缓存解决循环依赖,并且只能解决Setter注入的循环依赖。

2.2.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相关接口时,就将其自身注入到业务对象中即可。

2.2.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就是在这一步实现的偷梁换柱,产生对于原生对象的代理对象,然后将对源对象上的方法调用,转而使用代理对象的相同方法调用实现的。

2.2.6 自定义初始化逻辑

在所有的准备工作完成之后,如果我们的Bean还有一定的初始化逻辑,那么Spring将允许我们通过两种方式配置我们的初始化逻辑:(1)InitializingBean (2)配置init-method参数,一般通过配置init-method方法比较灵活。

2.2.7 BeanPostProcess后置处理

与前置处理类似,这里是在Bean自定义逻辑也执行完成之后,Spring又留给我们的最后一个扩展点。我们可以在这里在做一些我们想要的扩展。

2.2.8 自定义销毁逻辑

这一步对应自定义初始化逻辑,同样有两种方式:(1)实现DisposableBean接口 (2)配置destory-method参数。这里一个比较典型的应用就是配置dataSource的时候destory-method为数据库连接的close()方法。

2.2.9 调用回调销毁接口

Spring的Bean在为我们服务完之后,马上就要消亡了(通常是在容器关闭的时候),别忘了我们的自定义销毁逻辑,这时候Spring将以回调的方式调用我们自定义的销毁逻辑,然后Bean就这样走完了光荣的一生。我们再通过一张图来一起看一看Bean实例化阶段的执行顺序是如何的?

 

需要指出,容器启动阶段与Bean实例化阶段之间的桥梁就是我们可以选择自定义配置的延迟加载策略,如果我们配置了Bean的延迟加载策略,那么只有我们在真实的使用依赖对象的时候,Spring才会开始Bean的实例化阶段。而如果我们没有开启Bean的延迟加载,那么在容器启动阶段之后,就会紧接着进入Bean实例化阶段,通过隐式的调用getBean方法,来实例化相关Bean。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/679368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年6月GESP能力等级认证Python一级真题

2023-06 GESP等级考Python一级真题 题数&#xff1a;27 分数&#xff1a;100 测试时长&#xff1a;90min 一、单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09; 1. 以下不属于计算机输出设备的有&#xff08; A&#xff09;。&#xff08;2分&#xff09; A…

几个Arcpy代码应用案例

1 案例一 删除特定记录 使用 arcpy 从 ArcGIS 中的图层中删除特定记录。可以使用更新游标&#xff08;UpdateSursor&#xff09;和 SQL 查询来识别和删除所需的记录。以下是如何删除特定图层记录的示例 import arcpy selectedParcelsselectedParcels expres…

分子碰撞频率和自由程------从一个物理小问题解剖自己的数学思维

物理学12-8节中&#xff0c;关于分子平均碰撞的解说如下&#xff1a; &#xff08;一&#xff09;分子碰撞自由程公式 第一个公式中&#xff0c;lamda v / z中&#xff0c;v的单位是m/s, z的单位是A/s, 其中A是常数。那么lamda描述的结果是m/A, 这正是长度单位。 此公式lamda…

利用正弦定理证明两角和差公式

首先用正弦定理&#xff0c;证明sin(AB)sinAcosBcosAsinB。 另外&#xff0c;其它的两角和差公式&#xff0c;都可以用三角函数奇偶性、诱导公式等推导出来&#xff0c;无需再用正弦定理证明一遍。

耗时一个月!手撸博客系统,主打美观实用!

先附上博客链接RoCBlog 关于博客 关于博客 RoCBlog 完成耗时&#xff1a;20天 起初是想搭建自己的博客&#xff0c;看了网上许多开源框架&#xff0c;感觉没啥意思&#xff0c;于是决定自己写一套 纯手撸VueSpringboot 其实是个我一边学vue一边写出来的东西&#xff0c;前期…

团体程序设计天梯赛-练习集L1篇⑩

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

(贪心) 1221. 分割平衡字符串 ——【Leetcode每日一题】

❓ 1221. 分割平衡字符串 难度&#xff1a;简单 平衡字符串 中&#xff0c;L 和 R 字符的数量是相同的。 给你一个平衡字符串 s&#xff0c;请你将它分割成尽可能多的子字符串&#xff0c;并满足&#xff1a; 每个子字符串都是平衡字符串。 返回可以通过分割得到的平衡字符…

【JAVA反序列化】序列化与反序列化Java反射URLDNS链

文章目录 原生序列化与反序列化概述为什么需要序列化和反序列化&#xff1f;应用场景(涉及到将对象转换成二进制&#xff0c;序列化保证了能够成功读取到保存的对象)涉及的协议好处为什么会产生反序列化漏洞&#xff1f;可能反序列化的形式&#xff1f;代码演示 Java反射基础补…

Java try-catch块

Java的try块用于封装可能会抛出异常的代码。它必须在方法内部使用。 如果在try块中的特定语句处发生异常&#xff0c;后续的代码块将不会执行。因此&#xff0c;建议不要在try块中放置不会抛出异常的代码。 Java的try块必须后跟catch块或finally块。 Java try-catch语法 try…

JDBC ResultSet源码解读

通过Debug方式看下源码&#xff1a; 1.Debug程序&#xff0c;找到返回的 ResultSet 的引用 2. 查看ResultSet中的rowData 3. 查看 rows 我们可以看到rows是一个数组&#xff0c;里面存放了我们查询得到的数据&#xff08;我的表中一共7条数据&#xff09;。 4. 查看rows中的元…

【总结】网页状态码——200、302、304、400、404、405、500

200正常 500异常–服务器异常Java代码 细节&#xff1a;null和空&#xff0c;null调用方法会出现空指针异常 400异常----传参相关的异常 get方法长度限制 400异常&#xff0c;加了RequestParam(value “name”) 必须传值 400异常&#xff0c;后端类型是Integer&#xff0c…

c# 基于微信wechat_qrcode opencv插件,网络二维码图片批量识别(一)

一、基本概念 微信开源了其二维码的解码功能&#xff0c;并贡献给 OpenCV 社区。其开源的 wechat_qrcode 项目被收录到 OpenCV contrib 项目中。从 OpenCV 4.5.2 版本开始&#xff0c;就可以直接使用。 该项目 github 地址&#xff1a; https://github.com/opencv/opencv_contr…

中国电子学会2023年05月份青少年软件编程C++等级考试试卷二级真题(含答案)

一、编程题(共5题&#xff0c;共100分) 1. 数字放大 给定一个整数序列以及放大倍数x&#xff0c;将序列中每个整数放大x倍后输出。 时间限制&#xff1a;1000 内存限制&#xff1a;65536 【输入】 包含三行&#xff1a; 第一行为N&#xff0c;表示整数序列的长度(N ≤ 100)&a…

yandex地图js学习

由于百度地图在国外的某些寻路的场景不太完善&#xff0c;所以试用了一下俄罗斯的开源地图。同google地图一样&#xff0c;规划路线需要申请apikey&#xff0c;但无需绑定信用卡&#xff0c;每天的免费调用次数也非常够用。 yandex maps开发文档 申请apikey 只需要创建账号&…

5.5.4 从IPv4到IPv6过渡——双协议栈

5.5.4 从IPv4到IPv6过渡——双协议栈 与软件版本更新不同&#xff0c;IP协议版本的更新不可能在短时间内完成&#xff0c;只能够采用逐步演进的方法&#xff0c;也就是说在很长一段时间内&#xff0c;必须允许两种协议的网络并存&#xff0c;并且能够确保网络能够互联互通&…

C++11语法杂记(更新中)

文章目录 一. delctype二. default三. delete四. 可变参数模板五. emplace系列六. noexcept七. constexpr 一. delctype delctype和auto类似&#xff0c;也可以自动识别类型 举例如下&#xff1a; 与auto不同的是&#xff0c;auto只能用于定义变量类型&#xff0c;而decltyp…

查看ResultSet中的rowData

之前的dml语句都返回值都是int表示有改动的行数 那么Select可是要展示数据的 SELECT查询的结果 通过这个ResultSet遍历 然后通过next方法来一行行读取数据 类似于迭代器(肯定不是迭代器) 里面还包含获得的数据元素 相当于这个既包含元素&#xff0c;还能迭代自己的元素 具体你…

MySQL——DQL,DCL语言学习

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 前言 本章将会讲解MySQLDQL&#xff0c;DCL语言的学习。 一.DQL DQL 英文全称是 Data …

全志V3S嵌入式驱动开发(五种镜像烧入的方法)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 关于v3s的镜像烧入&#xff0c;大约有五种方法。前面陆陆续续已经介绍了三种方法&#xff0c;实际工作中具体使用哪一种&#xff0c;大家可以根据自…

SpringBoot 监控与管理的主要组件

SpringBoot 监控与管理的主要组件 SpringBoot作为一款开箱即用的Java Web开发框架&#xff0c;集成了很多方便开发者使用的组件。其中一个非常重要的组件就是监控与管理组件。在现代的分布式应用中&#xff0c;监控与管理已经成为了必不可少的一部分。SpringBoot提供了一些非常…