当前位置: 首页 > news >正文

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()方法来启动的。主要包括:

  1. resource定位过程 桶装水,先找到水

  2. 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();}
    
  3. 向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后置处理器等。

主要涉及到方法:

  1. org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean // 获取Bean

  2. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean // 创建Bean

  3. 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);}}
    
  4. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean // 填充Bean

  5. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition) // Bean的初始化方法调用

    通过jdk反射机制得到Method对象,然后调用Bean定义中申明的初始化方法

  6. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods 执行Bean初始化方法

    1. 如果Bean实现了InitializingBean接口,则先执行afterPropertiesSet()方法

    2. 执行Bean的init方法(Bean定义的initMethodName)

IOC容器中Bean生命周期

  1. Bean实例的创建
  2. 为Bean实例设置属性
  3. 调用Bean的初始化方法
  4. 应用使用Bean
  5. 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容器的实现 依赖反转&#xff1a; 依赖对象的获得被反转了&#xff0c;于是依赖反转更名为&#xff1a;依赖注入。许多应用都是由两个或者多个类通过彼此的合作来实现业务逻辑的&#xff0c;这使得每个对象都需要与其合作的对象的引用&#xff0c;如果这个获取过程需要自身…...

kotlin foreach 循环

java中的foreach循环也使用于kotlin &#xff0c;先回顾下java里面的foreach循环 java foreach循环格式 for(元素类型t 元素变量x : 遍历对象obj){引用了x的语句;} 例如&#xff1a; int[] intary {1,2,3,4};for (int a: intary) {Log.d("intary", String.value…...

分享相关知识

直接使用海龟图进行创作移动动态的游戏 这段代码是一个简单的turtle模块实现的小游戏&#xff0c;主要功能包括&#xff1a; 窗口和无人机初始化&#xff1a; 创建了一个turtle窗口&#xff0c;设置了窗口的背景颜色和标题。创建了一个表示无人机的turtle&#xff0c;形状为正…...

RabbitMQ(七)ACK 消息确认机制

目录 一、简介1.1 背景1.2 定义1.3 如何查看确认/未确认的消息数&#xff1f; 二、消息确认机制的分类2.1 消息发送确认1&#xff09;ConfirmCallback方法2&#xff09;ReturnCallback方法3&#xff09;代码实现方式一&#xff1a;统一配置a.配置类a.生产者c.消费者d.测试结果 …...

ubuntu 编译内核报错

Ubuntu 编译 Linux 内核经常会遇到如下错误&#xff1a; 如果报错 canonical-certs.pem&#xff1a; 如下&#xff1a; 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写的类库&#xff0c;可以方便的处理中文文本内容&#xff0c;是受到了TextBlob的启发而写的&#xff0c;由于现在大部分的自然语言处理库基本都是针对英文的&#xff0c;于是写了一个方便处理中文的类库&#xff0c;并且和TextBlob不同的是&…...

C# 语法进阶 委托

1.委托 委托是一个引用类型&#xff0c;其实他是一个类&#xff0c;保存方法的指针 &#xff08;指针&#xff1a;保存一个变量的地址&#xff09;他指向一个方法&#xff0c;当我们调用委托的时候这个方法就立即被执行 关键字&#xff1a;delegate 运行结果&#xff1a; 思…...

开源可观测性平台Signoz(四)【链路监控及数据库中间件监控篇】

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 前文链接&#xff1a; ​​开源可观测性平台Signoz系列&#xff08;一&#xff09;【开篇】​​ ​​开源可观测性平台Signoz&…...

【嵌入式开发 Linux 常用命令系列 4.2 -- git .gitignore 使用详细介绍】

文章目录 .gitignore 使用详细介绍.gitignore 文件的位置.gitignore 语法规则使用示例注意事项 .gitignore 使用详细介绍 .gitignore 文件是一个特殊的文本文件&#xff0c;它告诉 Git 哪些文件或目录是可以被忽略的&#xff0c;即不应该被纳入版本控制系统。这主要用于避免一…...

【熔断限流组件resilience4j和hystrix】

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容起因resilience4j落地实现pom.xml依赖application.yml配置接口使用 hystrix 落地实现pom.xml依赖启动类上添加注解接口上使用 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟…...

微服务雪崩问题及解决方案

雪崩问题 微服务中&#xff0c;服务间调用关系错综复杂&#xff0c;一个微服务往往依赖于多个其它微服务。 微服务之间相互调用&#xff0c;因为调用链中的一个服务故障&#xff0c;引起整个链路都无法访问的情况。 如果服务提供者A发生了故障&#xff0c;当前的应用的部分业务…...

008、所有权

所有权可以说是Rust中最为独特的一个功能了。正是所有权概念和相关工具的引入&#xff0c;Rust才能够在没有垃圾回收机制的前提下保障内存安全。 因此&#xff0c;正确地了解所有权概念及其在Rust中的实现方式&#xff0c;对于所有Rust开发者来讲都是十分重要的。在本文中&…...

千里马2023年终总结-android framework实战

背景&#xff1a; hi粉丝朋友们&#xff1a; 2023年马上就过去了&#xff0c;很多学员朋友也都希望马哥这边写个年终总结&#xff0c;因为这几个月时间都忙于新课程halsystracesurfaceflinger专题的开发&#xff0c;差点都忘记了这个事情了&#xff0c;今天特别花时间来写个bl…...

vue3中pinia的使用及持久化(详细解释)

解释一下pinia&#xff1a; Pinia是一个基于Vue3的状态管理库&#xff0c;它提供了类似Vuex的功能&#xff0c;但是更加轻量化和简单易用。Pinia的核心思想是将所有状态存储在单个store中&#xff0c;并且将store的行为和数据暴露为可响应的API&#xff0c;从而实现数据&#…...

安装 yarn、pnpm、功能比较

安装 yarn 官网&#xff1a;https://classic.yarnpkg.com/ 快速、可靠和安全的依赖性管理。 Yarn是您代码的软件包管理器。它允许您使用和共享&#xff08;例如JavaScript&#xff09;与来自世界各地的其他开发人员一起编写代码。Yarn是一个新的快速安全可信赖的可以替代 NP…...

计算机专业个人简历范文(8篇)

HR浏览一份简历也就25秒左右&#xff0c;如果你连「好简历」都没有&#xff0c;怎么能找到好工作呢&#xff1f; 如果你不懂得如何在简历上展示自己&#xff0c;或者觉得怎么改简历都不出彩&#xff0c;那请你一定仔细读完。 互联网运营个人简历范文> 男 22 本科 AI简历…...

几个实用网站

论文短语&#xff1a;https://www.phrasebank.manchester.ac.uk/ 翻译&#xff1a;https://www.deepl.com/en/translator 润色&#xff1a;https://quillbot.com/ 榜单&#xff1a;www.paperwithcode.com ****NLP民工的乐园: 几乎最全的中文NLP资源库&#xff1a;****https…...

Pycharm 切换interpreter---python的环境和第三方库问题

这篇回答两个问题&#xff1a; 1.为什么在 pycharm中打开新的project&#xff0c;切换interpreter 之后发现自己之前装的库消失了&#xff1f; 2.为什么 interpreter 切换到python3.8了&#xff0c; terminal 还是在 3.9&#xff1f;&#xff1f; 问题的关键&#xff1a;搞懂什…...

TP-LINK 路由器忘记密码 - 恢复出厂设置

TP-LINK 路由器忘记密码 - 恢复出厂设置 1. 恢复出厂设置2. 创建管理员密码3. 上网设置4. 无线设置5. TP-LINK ID6. 网络状态References 1. 恢复出厂设置 在设备通电的情况下&#xff0c;按住路由器背面的 Reset 按钮直到所有指示灯同时亮起后松开。 2. 创建管理员密码 3. 上网…...

关闭 Elasticsearch 集群的安全性设置

关闭 Elasticsearch 集群的安全性设置&#xff0c;特别是如果您正在使用 X-Pack&#xff0c;涉及到修改 Elasticsearch 的配置。以下是一般步骤&#xff0c;但请注意&#xff0c;这可能会使您的 Elasticsearch 集群面临安全风险&#xff0c;因此建议仅在开发或测试环境中执行此…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用

一、方案背景​ 在现代生产与生活场景中&#xff0c;如工厂高危作业区、医院手术室、公共场景等&#xff0c;人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式&#xff0c;存在效率低、覆盖面不足、判断主观性强等问题&#xff0c;难以满足对人员打手机行为精…...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区&#xff08;Partitioning&#xff09;是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分&#xff08;分区&#xff09;可以独立存储、管理和优化&#xff0c;…...

沙箱虚拟化技术虚拟机容器之间的关系详解

问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西&#xff0c;但是如果把三者放在一起&#xff0c;它们之间到底什么关系&#xff1f;又有什么联系呢&#xff1f;我不是很明白&#xff01;&#xff01;&#xff01; 就比如说&#xff1a; 沙箱&#…...

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...

SpringCloud优势

目录 完善的微服务支持 高可用性和容错性 灵活的配置管理 强大的服务网关 分布式追踪能力 丰富的社区生态 易于与其他技术栈集成 完善的微服务支持 Spring Cloud 提供了一整套工具和组件来支持微服务架构的开发,包括服务注册与发现、负载均衡、断路器、配置管理等功能…...