简述Spring中IOC核心流程

news2024/10/5 22:23:32

基础概念

IOC 和 DI

IOC,即控制反转。是Spring的一种设计思想。传统程序设计中,我们创建一个对象是通过 new 关键字,是程序主动去创建依赖对象,而在spring中专门有一个容器来创建和管理这些对象,并将对象依赖的其他对象注入到该对象中,这就是我们经常说到的 IOC 容器
所有类的创建、销毁都由spring来控制。对于某一个具体对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫做控制反转

DI,即依赖注入。可以说IOC和DI是同一概念的不同角度描述。
依赖注入是指组件之间的依赖关系由容器在运行期决定,也就是由容器动态将某个依赖关系注入到组件之中。依赖注入的目的不是为了带来更多的功能,而是为了提升组件重用的频率,通过依赖注入机制,我们只需要简单的配置就可以实现

bean

由spring IOC 容器管理的对象称之为 bean。bean是一个由IOC容器进行实例化、组装和管理的对象。
例如我们经常使用到的 @Service 注解,这个注解就相当于告诉 IOC 容器,这个类需要你帮我创建和管理

BeanDefinition

beanDefinition 就是 bean 的定义信息,用来存储 bean 的所有属性方法定义

BeanFactory 和 ApplicationContext

BeanFactory :它是一个用于访问 IOC 容器的根接口,提供了完整的 IOC 服务支持
ApplicationContext:BeanFactory 的子接口,在 BeanFactory 的所有功能上,还额外新增了其他高级的特性,比如:事件发布、国际化支持、统一资源加载策略等。正常情况下,我们使用的都是 ApplicationContext。
以电话来举例:

我们家里使用的 “座机” 就类似于 BeanFactory,可以进行电话通讯,满足了最基本的需求。

而现在非常普及的智能手机,iPhone、小米等,就类似于 ApplicationContext,除了能进行电话通讯,还有其他很多功能:拍照、地图导航、听歌等。

FactoryBean

一般情况下,我们将 bean 的创建和管理都交给 IOC 容器,spring 会利用反射来实例化bean对象,但是如果我们想自己实现 bean 的创建操作,可以实现吗?肯定可以,FactoryBean就实现了这个需求。
FactoryBean 是一个特殊的 bean ,接口中第一个三个方法 isSingleton()、getObjectType()、getObject() ,它是一个工厂bean,可以自己创建 bena实例,如果一个类实现了 FactoryBean接口,则该类可以自己定义创建实例对象的方法,只需要实现它的 getObject() 方法即可

下面将介绍 Spring IOC 的核心流程。

Spring IOC的核心方法就在于 refresh 方法,这个方法里面完成了 Spring的初始化、准备bean、实例化bean和扩展功能的实现。

refresh 方法

在ConfigurableApplicationContext里面定义了这个方法:
在这里插入图片描述
根据注释可以知道,这个方法是用来加载刷新配置,这些配置可能来自java配置、xml文件、properties文件、关系型数据库或者其他格式。
作为一个启动方法,它应当在初始化失败后销毁已经创建的单例,防止占着资源而不使用。也就是说调用这个方法的话,要么所有的单例已经被实例化,要么所有的单例都不存在。
BeansException:bean工厂不能被初始化,抛出BeansException
IllegalStateException:bean工厂已经被初始化了,但是不支持多次刷新,抛出IllegalStateException

具体实现

在 AbstractApplicationContext 中进行了实现:

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			/**
			 * 前戏,做容器刷新前的准备工作
			 * 1、设置容器启动时间
			 * 2、设置活跃状态为true
			 * 3、设置关闭状态为false
			 * 4、获取Environment对象,并加载当前系统属性值到Environment对象中
			 * 5、准备监听器和事件的集合对象,默认为空集合
			 */
			prepareRefresh();

			/**
			 * 告诉子类刷新内部bean工厂
			 *
			 * 创建容器对象:DefaultListableBeanFactory
			 * 加载xml配置文件到BeanFactory中,最重要的就是BeanDefinition
			 * 就是从xml中读取bean的定义信息,然后舍设置到 DefaultListableBeanFactory 中的 beanDefinitionMap 属性中
			 */
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			/**
			 * BeanFactory 的准备工作,对 BeanFactory 各种属性进行填充
			 */
			prepareBeanFactory(beanFactory);

			try {

				/**
				 * 子类覆盖方法做额外的处理,此处我们一般不需要做任何扩展工作
				 *
				 * 在 web 模块中有具体的实现
				 * org.springframework.web.context.support.AbstractRefreshableWebApplicationContext
				 */
				postProcessBeanFactory(beanFactory);

				/**
				 * 调用各种 beanDefinitionRegistryPostProcessor、beanFactoryPostProcessor
				 */
				invokeBeanFactoryPostProcessors(beanFactory);

				// 将所有实现了 BeanPostProcessor 接口的类注册到 BeanFactory 中,
				// registerBeanPostProcessors 方法只是注册到 BeanFactory 中,具体调用是在 bean 初始化的时候
				// 具体的:在所有 bean 实例化时,执行初始化方法前会调用所有 BeanPostProcessor 的 postProcessBeforeInitialization 方法,
				// 在执行初始化方法后会调用所有 BeanPostProcessor 的 postProcessAfterInitialization 方法。
				registerBeanPostProcessors(beanFactory);

				// 为上下文初始化message源,即不同语言的消息体,国际化处理
				initMessageSource();

				// 初始化事件监听多路广播器
				initApplicationEventMulticaster();

				// 留给子类来初始化其他的 bean
				onRefresh();

				// 在所有注册的 bean 中,查找 listener bean 注册到广播器中
				registerListeners();

				// 初始化剩下的单实例bean(非懒加载的)
				finishBeanFactoryInitialization(beanFactory);

				// 发布finishRefresh事件
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// 销毁所有已经创建了的单例bean
				destroyBeans();

				// 重置 active 标记
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

prepareRefresh():只是做一些容器刷新前的准备工作,无需过多关注,我们从第二个方法 obtainFreshBeanFactory() 往下看,这里只做简要分析,后面文章中会对每个方法做详细介绍:

初始化 BeanFactory、加载 Bean 定义

1、创建一个新的 BeanFactory,默认为 DefaultListableBeanFactory

2、读取 Spring 配置文件,并封装成 Resource。

3、根据 Resource 加载 XML 配置文件,并解析成 Document 对象 。

4、从根节点开始,遍历解析 Document 中的节点。

4.1、对于默认命名空间的节点:先将 bean 节点内容解析封装成 BeanDefinition,然后将 beanName、BeanDefinition 放到 BeanFactory 的缓存中,用于后续创建 bean 实例时使用。

4.2、对于自定义命名空间的节点:会拿到自定义命名空间对应的解析器,对节点进行解析处理。

例如:<context:component-scan base-package=“com.yao” /> ,该节点对应的解析器会扫描 base-package 指定路径下的所有类,将使用了 @Component(@Controller、@Service、@Repository)注解的类封装成 BeanDefinition,然后将 beanName、BeanDefinition 放到 BeanFactory 的缓存中,用于后续创建 Bean 实例时使用。

触发 BeanFactoryPostProcessor

实例化和调用所有 BeanFactoryPostProcessor,包括其子类 BeanDefinitionRegistryPostProcessor

BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 时对外暴露的扩展点,SpringIOC容器允许 BeanFactoryPostProcessor 在容器实例化任何 bean 之前读取 bean 的定义,并可以修改它。

BeanDefinitionRegistryPostProcessor 继承自 BeanFactoryPostProcessor,比 BeanFactoryPostProcessor 具有更高的优先级,主要用来在常规的 BeanFactoryPostProcessor 激活之前注册一些 bean 定义。特别是,你可以通过 BeanDefinitionRegistryPostProcessor 来注册一些常规的 BeanFactoryPostProcessor,因为此时所有常规的 BeanFactoryPostProcessor 都还没开始被处理。

注:这边的 “常规 BeanFactoryPostProcessor” 主要用来跟 BeanDefinitionRegistryPostProcessor 区分。

Mybatis 中的 MapperScannerConfigurer 是一个典型的 BeanDefinitionRegistryPostProcessor 的扩展使用,有兴趣的可以看看这个类的源码。

注册 BeanPostProcessor

注册所有的 BeanPostProcessor,将所有实现了 BeanPostProcessor 接口的类加载到 BeanFactory 中。

BeanPostProcessor 接口是 Spring 初始化 bean 时对外暴露的扩展点,Spring IoC 容器允许 BeanPostProcessor 在容器初始化 bean 的前后,添加自己的逻辑处理。在这边只是注册到 BeanFactory 中,具体调用是在 bean 初始化的时候。

实例化所有剩余的非懒加载单例 bean

1、遍历所有被加载到缓存中的 beanName,触发所有剩余的非懒加载单例 bean 的实例化。

2、首先通过 beanName 尝试从缓存中获取,如果存在则跳过实例化过程;否则,进行 bean 的实例化。

3、根据 BeanDefinition,使用构造函数创建 bean 实例。

4、根据 BeanDefinition,进行 bean 实例属性填充。

5、执行 bean 实例的初始化。

5.1、触发 Aware 方法。

5.2、触发 BeanPostProcessor 的 postProcessBeforeInitialization 方法。

5.3、如果 bean 实现了 InitializingBean 接口,则触发 afterPropertiesSet() 方法。

5.4、如果 bean 设置了 init-method 属性,则触发 init-method 指定的方法。

5.5、触发 BeanPostProcessor 的 postProcessAfterInitialization 方法。

6、将创建好的 bean 实例放到缓存中,用于之后使用。

完成上下文的刷新

使用应用事件广播器推送上下文刷新完毕事件(ContextRefreshedEvent )到相应的监听器。

至此,整个 IoC 的核心流程介绍完毕。

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

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

相关文章

rpm详解——原理,命令详细讲解

目录 三个问题 什么是RPM SRPM (Source RPM) RPM软件包 RPM 查询命令 查询已安装的RPM软件信息 查询文件/目录属于哪个RPM软件 查询未安装的RPM包文件 RPM 软件包的安装、卸载 安装 卸载 RPM 升级与更新 三个问题 先抛出三个问题&#xff0c;最后简单解释一下。 …

HiveSQL 电商场景TopK统计

数据准备 CREATE TABLE test.test2 ( user_id string, shop string ) ROW format delimited FIELDS TERMINATED BY \t; INSERT INTO TABLE test.test2 VALUES ( u1, a ), ( u2, b ), ( u1, b ), ( u1, a ), ( u3, c ), ( u4, b ), ( u1, a ), ( u2, c ), ( u5, b ), ( u4, b )…

基于人工智能的AI理发师能帮托尼老师做什么?

BarberGPT是一个人工智能理发师&#xff0c;它可以让您在照片上尝试不同的发型。您只需要上传您的照片&#xff0c;标记您的头发&#xff0c;然后就可以看到惊人的变化。BarberGPT使用了先进的深度学习技术&#xff0c;可以根据您的脸型、肤色和发质生成适合您的发型。BarberGP…

国产监控数据库产品lepus最新版学习和部署(V5.1)

目录 lepus是什么&#xff1f; 二进制安装&#xff08;最新v5.1&#xff09; 1.基础环境 2.安装NSQ 3.NSQ消息测试 4.安装lepus 5.初始化数据库 6.修改配置文件 7.启动服务组件 8.安装lepus-console 9.运行和访问控制台 lepus是什么&#xff1f; Lepus数据库监控系统…

Docker容器应用为工业连接提供了新选择

一 智能数据管理 Docker容器应用可灵活部署在异构计算平台上&#xff0c;且仅需占用少量的资源&#xff0c;这可为工业4.0提供一种新的数据集成方案。Docker容器应用程序是提供特定功能的小型软件模块&#xff0c;可在自动化领域中用于智能数据管理。而Softing推出的一个新产品…

简要介绍 | CUDA底层原理:加速高性能计算的关键技术

注1&#xff1a;本文系“简要介绍”系列之一&#xff0c;仅从概念上对CUDA的底层原理进行非常简要的介绍&#xff0c;不适合用于深入和详细的了解。 CUDA底层原理&#xff1a;加速高性能计算的关键技术 CUDA Refresher: The GPU Computing Ecosystem | NVIDIA Technical Blog 1…

通过SECS/GEM平台实现半导体设备与EAP系统互联

在半导体电子和光伏行业大量的设备需要通过SECS/GEM协议与EAP系统进行互联。 常见的设备互联需求主要分为以下几类&#xff1a; 1.生产过程自动化&#xff1a;设备通过EAP下发指令进行切换程序、条码或RFID标签采集、Foup自动加载与卸载、晶圆生产加工自动化&#xff0c;减少…

6 种方式读取 Springboot 的配置,老司机都这么玩(原理+实战)

从配置文件中获取属性应该是SpringBoot开发中最为常用的功能之一&#xff0c;但就是这么常用的功能&#xff0c;仍然有很多开发者在这个方面踩坑。 我整理了几种获取配置属性的方式&#xff0c;目的不仅是要让大家学会如何使用&#xff0c;更重要的是弄清配置加载、读取的底层…

Windows WSL子系统Ubuntu22.04安装Nvidia显卡驱动

最近在研究AI&#xff0c;如果在Linux系统中部署的话需要重装系统&#xff0c;有些麻烦&#xff0c;又不想折腾。所以闲置很久没研究的WSL又拿起来研究了&#xff0c;当然部署ai还需要显卡驱动的支持&#xff0c;就必须先安装显卡驱动。 还没有安装过WSL的童鞋可以看我之前发布…

在 javascript 中清除 canvas 画布

文章目录 在 JavaScript 中清除画布在 HTML 中使用 JavaScript 创建画布 我们使用 canvas 来绘制图形。 它提供了多种绘制的方法&#xff0c;如圆形、方框、文字、添加图片等&#xff0c;我们在使用canvas时需要清除它并在上面绘制。 本文介绍如何在 JavaScript 中清除画布。 …

LogicFlow:自定义 bpmn 用户节点(1)

切入正题之前&#xff0c;首先我们先了解下面板上面节点的几个重要属性&#xff0c;如下图&#xff1a; 虽然 logicflow 内置插件里面有用户节点&#xff08;bpmn:userTask&#xff09;&#xff0c;但是你若是想实现下面这种形式&#xff0c;就需要自己重新写个节点了。 上面…

MongoDB_5.0.18下载及安装(CentOS7)

文章目录 MongoDB安装&#xff08;Centos7&#xff09;1、下载地址2、安装流程2.1 下载server包2.2 安装server包2.3 修改默认mongod.conf配置文件2.4 启动服务2.5 这样就可以通过远程工具进行链接使用&#xff0c;创建mongodb用户&#xff0c;详细请查看其他文档 3、分享远程工…

球王梅西的力量

这次阿根廷来中国参加友谊赛&#xff0c;场内场外都有很多值得大众思考的&#xff0c;无论是对主办方的各种吐槽&#xff0c;还是对球迷近乎疯狂的追星行为&#xff0c;又或者是疫情放开后&#xff0c;大众积压已久的情绪&#xff0c;好象以往国外球队来&#xff0c;无论是顶级…

Axure教程——滑动拼图解锁

本文将教大家如何用AXURE中的动态面板制作拼图解锁 一、效果 预览地址&#xff1a;https://74wxu6.axshare.com 二、功能 拖动图片到指定位置提示“验证成功”&#xff0c;如果没到指定位置则提示“验证没成功&#xff0c;请重新验证” 三、制作 1、制作拼图 加入底部验证图…

【Python 随练】乒乓球比赛名单

题目&#xff1a; 两个乒乓球队进行比赛&#xff0c;各出三人。甲队为 a,b,c 三人&#xff0c;乙队为 x,y,z 三人。已抽签决定比赛名单。有人向队员打听比赛的名单。a 说他不和 x 比&#xff0c;c 说他不和 x,z 比&#xff0c;请编程序找出三队赛手的名单。 简介&#xff1a;…

【软考-中级】系统集成项目管理工程师【总】

持续更新中。。。。。。。。。。。。。。。 学习目标&#xff1a;完成2023上半年 软件中考任务 目标23年5月 考试前 完成 相关知识点学习 和练习 核心 十五至尊图&#xff1a; 上面图是考试的核心&#xff0c;需要背下来 一、信息化知识&#xff08;重点&#xff09;一般…

Selenium Web自动化测试框架实践

目录 前言&#xff1a; 项目背景 功能实现 项目架构 浏览器driver定义 用例运行前后的环境准备工作 工具方法模块 Pageobject页面对象封装 执行测试用例   前言&#xff1a; Selenium是一个基于Web的自动化测试框架&#xff0c;可以通过模拟用户在浏览器上的操作&#xff0c;…

直接选择排序及其稳定性分析

直接选择排序 直接选择排序是一种很直观的排序方法。其操作是这样&#xff1a;先在未排序的序列中选择最小的元素&#xff08;或最大的元素&#xff09;&#xff0c;把它与第一个元素交换&#xff0c;放在第一个位置&#xff0c;再在剩余未排序序列中选择第二小的&#xff0c;…

驾驭Dubbo:探索其核心概念与突出特性,助力分布式系统升级

1、Dubbo简介 Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力&#xff0c; 利用 Dubbo 提供的丰富服…

147-Prolixe-KeygenMe#1

Exeinfo查壳&#xff0c;发现没有壳&#xff0c;是Delphi程序 OD字符串搜索&#xff0c;找到关键位置&#xff0c;发现一个关键的call&#xff0c;和一个strcmp比较函数。 进入关键call进行分析&#xff0c;结合IDA。 分析这个call&#xff0c;发现输入的name字符串长度必须…