Spring技术内幕笔记之IOC的实现
IOC容器的实现
依赖反转:
依赖对象的获得被反转了,于是依赖反转更名为:依赖注入。许多应用都是由两个或者多个类通过彼此的合作来实现业务逻辑的,这使得每个对象都需要与其合作的对象的引用,如果这个获取过程需要自身实现,那么这将导致代码高度耦合并且难以测试。 ----维基百科
依赖控制反转有很多实现方式。在Spring中,IOC容器是实现这个模式的载体,它可以在对象生成或初始化时直接将数据注入到对象中,也可以通过对象引用注入到对象数据域中的方式来注入对方法调用的依赖。这种依赖注入是可以递归的,对象被逐层注入。
IOC容器系列的设计与实现:BeanFactory 和 ApplicationContext
Spring IOC容器的设计中,主要两个主要的容器系列:
一个是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器的最基本的功能;
另一个是ApplicationContext应用上下文,他作为容器的高级形态而存在。
具体什么是容器呢?它在Spring框架中到底长什么样?
其实对IOC容器的使用者来说,我们经常接触到的BeanFactory 和 ApplicationContext都可以看成是容器具体表现形式。ApplicationContext是BeanFactory的子接口,BeanFactory提供了配置和基本功能,ApplicationContext添加了更多企业特定的功能,ApplicationContext是BeanFactory的完整超集。在Spring中,Spring有各式各样的IOC容器的实现供用户选择和使用。
ApplicationContext接口表示Spring IOC容器,负责实例化、配置和组装bean。容器通过读取配置元数据来获取实例化、配置和组装对象的指令。配置元数据用XML、Java注解或Java代码表示。它允许组成应用程序的对象以及这些对象之间丰富的相互依赖关系。
Spring提供了几个ApplicationContext接口的实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。虽然XML一直是定义配置元数据的传统格式,但可以通过提供少量XML配置以声明式地启用对这些附加元数据形式的支持,来指示容器使用Java注解或代码作为元数据格式
BeanDefinition
Spring通过BeanDefinition来管理基于Spring的应用中的各种对象以及它们之间的相互依赖关系。BeanDefinition抽象了我们对Bean的定义,是让容器起作用的主要数据类型。IOC容器是用来管理对象依赖关系的,对IOC容器来说,BeanDefinition就是对依赖反转模式中管理的对象依赖关系的数据抽象,也是容器实现依赖反转功能的核心数据结构,依赖反转功能都是围绕对这个BeanDefinition的处理来完成的。
Spring IOC容器的设计

BeanFactory的应用场景
BeanFactory接口定义了IOC容器最基本的形式,并且提供了IOC容器所应该遵守的最基本的服务契约。
BeanFactory主要接口:
public interface BeanFactory {String FACTORY_BEAN_PREFIX = "&";Object getBean(String name) throws BeansException;<T> T getBean(String name, Class<T> requiredType) throws BeansException;Object getBean(String name, Object... args) throws BeansException;boolean containsBean(String name);boolean isSingleton(String name) throws NoSuchBeanDefinitionException;boolean isPrototype(String name) throws NoSuchBeanDefinitionException;boolean isTypeMatch(String name, Class targetType) throwsNoSuchBeanDefinitionException;Class getType(String name) throws NoSuchBeanDefinitionException;String[] getAliases(String name);
}
ApplicationContext的应用场景
ApplicationContext是一个高级形态意义的IOC容器,ApplicationContext在BeanFactory的基础上添加了很多附加功能。
IOC容器的初始化过程
简单来说,IOC容器的初始化是由refresh()方法来启动的。主要包括:
-
resource定位过程 桶装水,先找到水
-
BeanDefinition载入 水处理成合格的水
相当于把定义的BeanDefinition在IOC容器中转化成一个Spring内部表示的数据结构的过程。IOC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相关相关操作来完成的。这些BeanDefinition数据在IOC容器中通过一个HashMap来保持和维护。
refresh()方法执行过程
prepareRefresh(); //这里是在子类中启动refreshBeanFactory()的地方 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory);try {//设置BeanFactoy的后置处理postProcessBeanFactory(beanFactory);//调用BeanFactory的后处理器,这些后处理器是在Bean定义中向容器注册的invokeBeanFactoryPostProcessors(beanFactory);//注册Bean的后处理器,在Bean创建过程中调用。registerBeanPostProcessors(beanFactory);//对上下文中的消息源进行初始化initMessageSource();//初始化上下文中的事件机制initApplicationEventMulticaster();//初始化其他的特殊BeanonRefresh();//检查监听Bean并且将这些Bean向容器注册registerListeners();//实例化所有的(non-lazy-init)单件finishBeanFactoryInitialization(beanFactory);//发布容器事件,结束Refresh过程finishRefresh();} catch (BeansException ex) {//为防止Bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件BeandestroyBeans();} -
向IOC容器注册这些BeanDefinition 将水装入桶中
将BeanDefinition放入值beanDefinitionMap Map中
org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
总结:IOC容器的初始化过程主要是工作是在IOC容器中建立BeanDefinition数据映射
==注意:Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器搜索Bean的时候(除了layzinit设置为true,设置了lazyinit属性那么这个Bean的依赖注入在IOC容器初始化时就预先完成了无需等待初始化完成后使用getBean去触发)。==
补充:lazy-init属性的处理,其实是直接采用getBean去触发依赖注入。所以说白了,与正常依赖注入的出发相比,只是触发的时间和场合不同,原理是一样的。
IOC容器的依赖注入
一句话:getBean是依赖注入的起点,之后会调用createBean,createBean不仅生成了Bean,还会对Bean初始化进行处理。比如实现了在BeanDefinition中的init-method属性定义,Bean后置处理器等。

主要涉及到方法:
-
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean // 获取Bean
-
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean // 创建Bean
-
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance // 创建Bean实例
提供了两种实例化Java对象方法,一种通过使用Java反射功能(BeanUtils.instantiateClass),一种是通过CGLIB生成。
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {// Don't override the class with CGLIB if no overrides.if (!bd.hasMethodOverrides()) {Constructor<?> constructorToUse;synchronized (bd.constructorArgumentLock) {constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;if (constructorToUse == null) {final Class<?> clazz = bd.getBeanClass();if (clazz.isInterface()) {throw new BeanInstantiationException(clazz, "Specified class is an interface");}try {if (System.getSecurityManager() != null) {constructorToUse = AccessController.doPrivileged((PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);}else {constructorToUse = clazz.getDeclaredConstructor();}bd.resolvedConstructorOrFactoryMethod = constructorToUse;}catch (Throwable ex) {throw new BeanInstantiationException(clazz, "No default constructor found", ex);}}}return BeanUtils.instantiateClass(constructorToUse);}else {// Must generate CGLIB subclass.return instantiateWithMethodInjection(bd, beanName, owner);}} -
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean // 填充Bean
-
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition) // Bean的初始化方法调用
通过jdk反射机制得到Method对象,然后调用Bean定义中申明的初始化方法
-
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods 执行Bean初始化方法
-
如果Bean实现了InitializingBean接口,则先执行afterPropertiesSet()方法
-
执行Bean的init方法(Bean定义的initMethodName)


-
IOC容器中Bean生命周期
- Bean实例的创建
- 为Bean实例设置属性
- 调用Bean的初始化方法
- 应用使用Bean
- Bean销毁
FatoryBean的实现
通过org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean方法得知,获取Bean核心是factory.getObject(),也就说通过实现FactoryBean接口,开放getOject方法,供使用者自由发挥使用。
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {Object object;try {if (System.getSecurityManager() != null) {AccessControlContext acc = getAccessControlContext();try {object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);}catch (PrivilegedActionException pae) {throw pae.getException();}}else {object = factory.getObject();}}catch (FactoryBeanNotInitializedException ex) {throw new BeanCurrentlyInCreationException(beanName, ex.toString());}catch (Throwable ex) {throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);}// Do not accept a null value for a FactoryBean that's not fully// initialized yet: Many FactoryBeans just return null then.if (object == null) {if (isSingletonCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName, "FactoryBean which is currently in creation returned null from getObject");}object = new NullBean();}return object;}
可看出此时getBean是通过getObject()方法获取到的Bean

BeanPostProcessor的实现
简称“BPP” ,BPP是使用IOC容器时经常使用到的一个特性,这个Bean的后置处理器是一个监听器,可以监听容器触发的事件。将它向IOC容器注册后,容器中管理的Bean具备了接收IOC容器事件回调的能力。BPP是一个接口,主要只有以下两个方法:
public interface BeanPostProcessor {/*** 在Bean的初始化前提供回调入口*/@Nullabledefault Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}/*** 在Bean的初始化后提供回调入口*/@Nullabledefault Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}}
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition) 初始化Bean

如何与IOC结合?

autowiring(自动依赖装配)的实现
只有在类变量属性为类,此时需要在IOC容器中寻找依赖的对象,由此可推出自动依赖装配的过程是在属性填充过程中即populateBean()方法中实现。
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean

在populateBean方法中可找到autowireByName与autowireByType两个方法,通过类名/类型进行自动装配
autowireByName通过名称自动注入,直接根据属性名getBean()从IOC容器中获取Bean即可。

autowireByType稍微复杂一些,但是最终依然是通过getBean从IOC容器中获取Bean。
参考:
Spring技术内幕:深入解析Spring架构与设计原理(第2版)
相关文章:
Spring技术内幕笔记之IOC的实现
IOC容器的实现 依赖反转: 依赖对象的获得被反转了,于是依赖反转更名为:依赖注入。许多应用都是由两个或者多个类通过彼此的合作来实现业务逻辑的,这使得每个对象都需要与其合作的对象的引用,如果这个获取过程需要自身…...
kotlin foreach 循环
java中的foreach循环也使用于kotlin ,先回顾下java里面的foreach循环 java foreach循环格式 for(元素类型t 元素变量x : 遍历对象obj){引用了x的语句;} 例如: int[] intary {1,2,3,4};for (int a: intary) {Log.d("intary", String.value…...
分享相关知识
直接使用海龟图进行创作移动动态的游戏 这段代码是一个简单的turtle模块实现的小游戏,主要功能包括: 窗口和无人机初始化: 创建了一个turtle窗口,设置了窗口的背景颜色和标题。创建了一个表示无人机的turtle,形状为正…...
RabbitMQ(七)ACK 消息确认机制
目录 一、简介1.1 背景1.2 定义1.3 如何查看确认/未确认的消息数? 二、消息确认机制的分类2.1 消息发送确认1)ConfirmCallback方法2)ReturnCallback方法3)代码实现方式一:统一配置a.配置类a.生产者c.消费者d.测试结果 …...
ubuntu 编译内核报错
Ubuntu 编译 Linux 内核经常会遇到如下错误: 如果报错 canonical-certs.pem: 如下: make[1]: *** No rule to make target ‘debian/canonical-certs.pem’, needed by ‘certs/x509_certificate_list’. Stop. make: *** [Makefile:1868: …...
Python之自然语言处理库snowNLP
一、介绍 SnowNLP是一个python写的类库,可以方便的处理中文文本内容,是受到了TextBlob的启发而写的,由于现在大部分的自然语言处理库基本都是针对英文的,于是写了一个方便处理中文的类库,并且和TextBlob不同的是&…...
C# 语法进阶 委托
1.委托 委托是一个引用类型,其实他是一个类,保存方法的指针 (指针:保存一个变量的地址)他指向一个方法,当我们调用委托的时候这个方法就立即被执行 关键字:delegate 运行结果: 思…...
开源可观测性平台Signoz(四)【链路监控及数据库中间件监控篇】
转载说明:如果您喜欢这篇文章并打算转载它,请私信作者取得授权。感谢您喜爱本文,请文明转载,谢谢。 前文链接: 开源可观测性平台Signoz系列(一)【开篇】 开源可观测性平台Signoz&…...
【嵌入式开发 Linux 常用命令系列 4.2 -- git .gitignore 使用详细介绍】
文章目录 .gitignore 使用详细介绍.gitignore 文件的位置.gitignore 语法规则使用示例注意事项 .gitignore 使用详细介绍 .gitignore 文件是一个特殊的文本文件,它告诉 Git 哪些文件或目录是可以被忽略的,即不应该被纳入版本控制系统。这主要用于避免一…...
【熔断限流组件resilience4j和hystrix】
文章目录 🔊博主介绍🥤本文内容起因resilience4j落地实现pom.xml依赖application.yml配置接口使用 hystrix 落地实现pom.xml依赖启动类上添加注解接口上使用 📢文章总结📥博主目标 🔊博主介绍 🌟我是廖志伟…...
微服务雪崩问题及解决方案
雪崩问题 微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。 微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。 如果服务提供者A发生了故障,当前的应用的部分业务…...
008、所有权
所有权可以说是Rust中最为独特的一个功能了。正是所有权概念和相关工具的引入,Rust才能够在没有垃圾回收机制的前提下保障内存安全。 因此,正确地了解所有权概念及其在Rust中的实现方式,对于所有Rust开发者来讲都是十分重要的。在本文中&…...
千里马2023年终总结-android framework实战
背景: hi粉丝朋友们: 2023年马上就过去了,很多学员朋友也都希望马哥这边写个年终总结,因为这几个月时间都忙于新课程halsystracesurfaceflinger专题的开发,差点都忘记了这个事情了,今天特别花时间来写个bl…...
vue3中pinia的使用及持久化(详细解释)
解释一下pinia: Pinia是一个基于Vue3的状态管理库,它提供了类似Vuex的功能,但是更加轻量化和简单易用。Pinia的核心思想是将所有状态存储在单个store中,并且将store的行为和数据暴露为可响应的API,从而实现数据&#…...
安装 yarn、pnpm、功能比较
安装 yarn 官网:https://classic.yarnpkg.com/ 快速、可靠和安全的依赖性管理。 Yarn是您代码的软件包管理器。它允许您使用和共享(例如JavaScript)与来自世界各地的其他开发人员一起编写代码。Yarn是一个新的快速安全可信赖的可以替代 NP…...
计算机专业个人简历范文(8篇)
HR浏览一份简历也就25秒左右,如果你连「好简历」都没有,怎么能找到好工作呢? 如果你不懂得如何在简历上展示自己,或者觉得怎么改简历都不出彩,那请你一定仔细读完。 互联网运营个人简历范文> 男 22 本科 AI简历…...
几个实用网站
论文短语:https://www.phrasebank.manchester.ac.uk/ 翻译:https://www.deepl.com/en/translator 润色:https://quillbot.com/ 榜单:www.paperwithcode.com ****NLP民工的乐园: 几乎最全的中文NLP资源库:****https…...
Pycharm 切换interpreter---python的环境和第三方库问题
这篇回答两个问题: 1.为什么在 pycharm中打开新的project,切换interpreter 之后发现自己之前装的库消失了? 2.为什么 interpreter 切换到python3.8了, terminal 还是在 3.9?? 问题的关键:搞懂什…...
TP-LINK 路由器忘记密码 - 恢复出厂设置
TP-LINK 路由器忘记密码 - 恢复出厂设置 1. 恢复出厂设置2. 创建管理员密码3. 上网设置4. 无线设置5. TP-LINK ID6. 网络状态References 1. 恢复出厂设置 在设备通电的情况下,按住路由器背面的 Reset 按钮直到所有指示灯同时亮起后松开。 2. 创建管理员密码 3. 上网…...
关闭 Elasticsearch 集群的安全性设置
关闭 Elasticsearch 集群的安全性设置,特别是如果您正在使用 X-Pack,涉及到修改 Elasticsearch 的配置。以下是一般步骤,但请注意,这可能会使您的 Elasticsearch 集群面临安全风险,因此建议仅在开发或测试环境中执行此…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合
作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...
【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?
FTP(File Transfer Protocol)本身是一个基于 TCP 的协议,理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况,主要原因包括: ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...
