简单了解IoC

IoC

什么是IoC?

IoC(Inversion of Control),即控制反转,这是一种设计思想,在Spring指将对象的控制权交给Spring容器,由容器来实现对象的创建、管理,程序员只需要从容器获取想要的对象就可以了。这里引申一个词叫DI(Dependency Injection),即依赖注入,他是IoC的一种具体实现方式,类似于Map和HashMap的关系,指对象创建时的属性赋值交给Spring容器来做。

还有一种理解是把IoC理解为Spring容器,底层是一个Map,key是beanName,value是bean。

为什么需要IoC?

  • 不用手动new对象,将对象创建和业务解耦

  • 无需关注对象创建的过程,只管使用就行

IoC怎么使用?

声明bean

xml文件

@Component及其衍生注解

@Configuration和@Bean

获取bean

beanFactory.getBean(String beanName)

applicationContext.getBean(String beanName)

属性注入注解方式

@Autowired、@Value

IoC底层实现?

IoC把对象控制权反转给Spring框架,那么我们就来关注一下Spring怎么实现bean的创建、管理等,再详细一些,bean从实例化 -> 属性赋值 -> 初始化 -> 使用 -> 销毁,这叫做bean的生命周期,Spring管理的bean主要走的就是这么个流程。

什么是属性赋值前面已经解释过了,实例化和初始化是怎么回事呢?

  • 实例化实际上就是bean的创建,给bean在内存中分配空间;

  • 初始化回调各种Aware接口、回调各种初始化方法、生成AOP代理对象也在该阶段进行,该阶段主要是完成初始化回调。

基本概念

接下来会介绍底层源码,在此之前会给出一些基本概念,以便更加清晰地了解源码。

  • BeanFactory:bean工厂,用来生成bean,故bean的实例化、属性赋值、初始化都在此进行

  • BeanDefinition:bean的定义信息,你想让BeanFactory生产bean,那么自然要有张图纸,这张图纸就是BeanDefinition

  • BeanPostProcessor:bean的增强器,用来执行一些增强方法

  • BeanFactoryPostProcessor:beanFactory的增强器,用来执行一些增强方法

bean生命周期详解

refresh ()的第11步finishBeanFactoryInitialization(beanFactory);这里会进行bean的生命周期

通过getBean(beanName);进行单例bean的初始化,这里有对工厂bean的特殊处理TODO

getBean顾名思义就是获取bean,引出两个问题,去哪里获取?获取不到怎么办?

  • Spring里面大量使用缓存,这里存储bean的缓存特指Map,通过beanName为key可以获取到对应的bean;

  • 如果本身缓存不存在bean,那么需要去创建bean并将其放到缓存中去。

走从缓存中拿的逻辑

getSingleton(String beanName)会尝试从缓存中获取bean,值得一提的是这里有三个缓存,俗称三级缓存,为什么要使用三级缓存,等到后面再说。

走bean创建的逻辑doCreateBean

getSingleton, ObjectFactory<?> singletonFactory)内部会调用createBean()去创建bean,并且创建后把bean放到缓存中去

接下来就是bean的生命周期了:

  • 实例化:一般是反射调用无参构造器创建对象,instanceWrapper = createBeanInstance(beanName, mbd, args);

  • 属性赋值:populateBean(beanName, mbd, instanceWrapper);

  • 初始化:对bean进行一些额外操作,exposedObject = initializeBean(beanName, exposedObject, mbd);

  • 销毁:

实例化详解

前面提到实例化时通过构造器实现,不通过直接new对象的方式,我们该如何创建对象呢?Spring用到了反射去实例化bean,要用反射自然少不了bean对应的Class对象,通过内存中的Class对象我们才能够获取到类的信息(一个对象的模板)从而创建对象。

  • 获取bean的Class对象

    • Class<?> beanClass = resolveBeanClass(mbd, beanName);

  • 还可能用工厂去创建bean

  • 可能这里会用有参构造器,如果你有编写有参构造器的话

  • 然后到是无参构造器构造

    • return instantiateBean(beanName, mbd);

    • constructorToUse = clazz.getDeclaredConstructor();

3种实例化方式

除了构造器之外可以使用工厂方法去进行bean的实例化,以下列出他们各自的优缺点

  • 构造器:反射使用类构造器

    • 优点:使用方便,简单明了

    • 缺点:如果创建逻辑复杂,无法定制化配置

  • 静态工厂:调用一个静态的方法

    • 优点:可以在创建bean的时候获取到一些静态资源

    • 缺点:需要提前写好,无法拓展

  • 普通工厂:调用一个普通的方法

    • 优点:适用于逻辑复杂的创建,如依赖注入或状态管理,如某个依赖需要通过入参判断注入哪个类

    • 缺点:相比构造器方式需要多写一个工厂类

总的来说,如果没有特别复杂的创建逻辑需求,直接用构造器创建就好。

策略模式

值得一提的是这里用到了策略模式

Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);

下面这张图是继承关系图:顶级策略接口InstantiationStrategy

image-20240620223742711

默认JDK的无参构造器就是在SimpleInstantiationStrategy类中实现,如果实现CGLIB的方式实例化,在CglibSubclassingInstantiationStrategy有内部类CglibSubclassCreator通过instantiate(@Nullable Constructor<?> ctor, Object... args)实现CGLIB的一个实例化操作

属性赋值详解

对象已经在内存中存在了,但是它的属性还是空的,我们需要对其进行属性赋值,populateBean(beanName, mbd, instanceWrapper);,可以思考几个问题,值存放在哪里?如何赋值?

这些值是存放在BeanDefinition的成员变量里面的MutablePropertyValues propertyValues,通过这个就可以获取到bean的属性,在xml中是以<property>标签表示,如果你需要用注解的方式实现,可以自定义注解,搭配自定义BeanFactoryPostProcessor

 @Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyIntegerValue {​int value() default 0;}​@Componentpublic class MyValueBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {​@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {try {BeanDefinition beanDefinition =  registry.getBeanDefinition("person");String beanClassName = beanDefinition.getBeanClassName();Class<?> beanClass = Class.forName(beanClassName);Field field = beanClass.getDeclaredField("num");MyIntegerValue annotation = field.getAnnotation(MyIntegerValue.class);int value = annotation.value();beanDefinition.getPropertyValues().getPropertyValueList().add(new               PropertyValue(field.getName(), value));} catch (NoSuchFieldException | ClassNotFoundException e) {throw new RuntimeException(e);}}}

如何赋值?

setPropertyValues(PropertyValues pvs)setPropertyValue(PropertyValue pv)

 protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {if (tokens.keys != null) {// 设置数组、List、Map,暂时TODOprocessKeyedProperty(tokens, pv);}else {// 设置普通值,就是调用反射,method.invoke(obj, arg)processLocalProperty(tokens, pv);}}

对于@Autowired的注解,通过InstantiationAwareBeanPostProcessor的postProcessProperties()方法进行赋值

 if (hasInstantiationAwareBeanPostProcessors()) {if (pvs == null) {pvs = mbd.getPropertyValues();}for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {// 对所有需要依赖检查的属性进行后处理PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}pvs = pvsToUse;}}

初始化详解

属性赋值之后,我们还需要对其进行初始化,可以调用初始化方法实现一些逻辑,例如资源分配或者初始化,如Spring Boot的自动配置类,exposedObject = initializeBean(beanName, exposedObject, mbd);

调用用户自己实现的初始化方法invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)

  1. 先调用 @PostConstructor 注解,在BeanPostProcessor前置增强执行

  2. 再调用实现 InitializingBean 接口的回调方法 afterPropertiesSet()

  3. 最后调用xml文件的 init-method 方法

初始化增强

在初始化前后,会对bean进行增强

前置增强

一般是一些Aware回调方法执行,如ApplicationContextAware的回调方法的执行,调用BeanPostProcessor增强processor.postProcessBeforeInitialization(result, beanName);

后置增强

这里会和AOP有关了,调用BeanPostProcessor增强,processor.postProcessAfterInitialization(result, beanName)

为什么使用三级缓存?
循环依赖

这个三级缓存其实和属性赋值那一步相关,缓存的是bean,那我们属性赋值的时候有可能是给基本属性赋值、也有可能是给引用属性赋值,在给引用属性赋值的时候我们会调用getBean(),是不是很熟悉,即创建bean的时候会调用的,从某种角度来看很像递归,肯会出现一直调用导致SOF,在Spring表现的就是会出现循环依赖问题,那我们就需要一个终止条件,即用缓存代替创建bean

具体是哪三级缓存?
 /** Cache of singleton objects: bean name to bean instance. */// 一级缓存,beanName -> 实例化并且初始化的成品private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);​/** Cache of singleton factories: bean name to ObjectFactory. */// 三级缓存,beanName -> ObjectFactoryprivate final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);​/** Cache of early singleton objects: bean name to bean instance. */// 二级缓存,beanName -> 实例化但是未初始化的成品private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

为什么用了三级缓存就没有循环依赖?

先看循环依赖的原因:假设有A,B两个类,他们互相依赖

1.实例化A

2.给A的b属性赋值

3.实例化B

4.给B的a属性赋值

5.重复第一步

那用二级缓存能否解决循环依赖呢?

1.实例化A

2.给A的b属性赋值

3.实例化B

4.给B的a属性赋值(注意,此时的a就不会再走到第五步去实例化了,而是从二级缓存中获取完成品A)

5.B的属性赋值完成,接下来回到第二步,即将A的属性赋值完成,自此循环依赖解决

那为什么是三级缓存呢?

想一想如果只有二级缓存,再加上AOP代理,此时B.a是半成品A,而A是个代理对象,这两个不相同,即b.getA() != A a

而A是个单例对象,这就很奇怪了吧。

那你可能又会想那实例化的时候我直接先把代理对象创建出来不久好了吗,不就一样了吗,但是objectFactory每次调用getObject()返回的都是新的代理对象,所以如果某个代理类C被当作很多个类的属性,那么这些类的这个引用属性C还是不相同的,所以我们需要三级缓存,有了二级就不用找三级,即代理对象只会被生产一次

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

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

相关文章

java设计模式(四)原型模式(Prototype Pattern)

1、模式介绍&#xff1a; 原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;它允许对象在创建新实例时通过复制现有实例而不是通过实例化新对象来完成。这样做可以避免耗费大量的资源和时间来初始化对象。原型模式涉及一个被复制的原型对象…

ES6模板字符串详解

ES6是JavaScript语言的一次重大更新&#xff0c;引入了许多新特性和语法改进&#xff0c;其中模板字符串是一个非常实用和灵活的语法特性。它可以让我们从数组或对象中提取值&#xff0c;并赋给对应的变量&#xff0c;让代码变得更加简洁和易读。 本文将深入探讨ES6解构赋值的语…

Nginx开发--动静分离和URLRewrite

05 【动静分离和URLRewrite】 1.动静分离介绍 为了提高网站的响应速度&#xff0c;减轻程序服务器&#xff08;Tomcat&#xff0c;Jboss等&#xff09;的负载&#xff0c;对于静态资源&#xff0c;如图片、js、css等文件&#xff0c;可以在反向代理服务器中进行缓存&#xff…

减少液氮罐内液氮损耗的方法

监测与管理液氮容器的密封性能 液氮容器的密封性能直接影响液氮的损耗情况。一个常见的损耗源是容器本身的密封不良或老化导致的泄漏。为了有效减少液氮损耗&#xff0c;首先应当定期检查液氮容器的密封性能。这可以通过简单的方法如肉眼检查外观&#xff0c;或者更精确的方法…

xxl-job 分布式任务调度 基本使用

xxl-job 是一个分布式任务调度平台&#xff0c;使用非常方便。 官网&#xff1a;https://gitee.com/xuxueli0323/xxl-job 工作原理类似于nacos 执行器注册到调度中心 调度中心分配任务 执行器执行任务 docker-compose 配置 version: 3 services:xxl-job:image: xuxueli/xxl-…

科普文:外贸垃圾邮件判定

国外垃圾邮件判定规则 很多时候&#xff0c;外贸的沟通多以邮件为主&#xff0c;他们作为专业的采购商&#xff0c;每天邮箱里都会塞满了邮件。因此&#xff0c;为了提高工作效率&#xff0c;很多国外客户喜欢使用垃圾邮件过滤器来过滤掉一部分垃圾邮件。 以下几种情况会触发垃…

《重构》读书笔记【第1章 重构,第一个示例,第2章 重构原则】

文章目录 第1章 重构&#xff0c;第一个示例1.1 重构前1.2 重构后 第2章 重构原则2.1 何谓重构2.2 两顶帽子2.3 为何重构2.4 何时重构2.5 重构和开发过程 第1章 重构&#xff0c;第一个示例 我这里使用的IDE是IntelliJ IDEA 1.1 重构前 plays.js export const plays {&quo…

AcWing算法基础课笔记——高斯消元

高斯消元 用来求解方程组 a 11 x 1 a 12 x 2 ⋯ a 1 n x n b 1 a 21 x 1 a 22 x 2 ⋯ a 2 n x n b 2 … a n 1 x 1 a n 2 x 2 ⋯ a n n x n b n a_{11} x_1 a_{12} x_2 \dots a_{1n} x_n b_1\\ a_{21} x_1 a_{22} x_2 \dots a_{2n} x_n b_2\\ \dots \\ a…

论文导读 | Manufacturing Service Operations Management近期文章精选

编者按 在本系列文章中&#xff0c;我们梳理了顶刊Manufacturing & Service Operations Management5月份发布有关OR/OM以及相关应用的文章之基本信息&#xff0c;旨在帮助读者快速洞察行业/学界最新动态。 推荐文章1 ● 题目&#xff1a;Robust Drone Delivery with Weath…

【C++题解】1712. 输出满足条件的整数2

问题&#xff1a;1712. 输出满足条件的整数2 类型&#xff1a;简单循环 题目描述&#xff1a; 有这样的三位数&#xff0c;其百位、十位、个位的数字之和为偶数&#xff0c;且百位大于十位&#xff0c;十位大于个位&#xff0c;请输出满所有满足条件的整数。 输入&#xff1…

#05搜索法

要点&#xff1a; ①搜索法&#xff1a;穷举搜索、深度优先搜索、广度优先搜索、广深结合搜索、回溯法、分支限界法&#xff1b; ②解空间树&#xff1a;子集树、排列树、满m叉树。 ③回溯法及分支限界法求解问题的方法与步骤。 难点&#xff1a; 子集树、排列树和满m叉树…

小程序下拉刷新,加载更多数据,移动端分页

文章目录 页面结构图WXML页面代码js代码wxss代码总结备注 参考&#xff1a;https://juejin.cn/post/7222855604406796346 页面结构图 一般页面就4个结构&#xff1a;最外滚动层、数据展示层、暂无数据层、没有更多数据层。 如图&#xff1a; WXML页面代码 <scroll-view …

Golang | Leetcode Golang题解之第191题位1的个数

题目&#xff1a; 题解&#xff1a; func hammingWeight(num uint32) (ones int) {for ; num > 0; num & num - 1 {ones}return }

# Kafka_深入探秘者(5):kafka 分区

Kafka_深入探秘者&#xff08;5&#xff09;&#xff1a;kafka 分区 一、kafka 副本机制 1、Kafka 可以将主题划分为多个分区(Partition)&#xff0c;会根据分区规则选择把消息存储到哪个分区中&#xff0c;只要如果分区规则设置的合理&#xff0c;那么所有的消息将会被均匀的…

Golang | Leetcode Golang题解之第198题打家劫舍

题目&#xff1a; 题解&#xff1a; func rob(nums []int) int {if len(nums) 0 {return 0}if len(nums) 1 {return nums[0]}first : nums[0]second : max(nums[0], nums[1])for i : 2; i < len(nums); i {first, second second, max(first nums[i], second)}return se…

openEuler离线安装nginx

目录 1.创建储存目录 2.切换到储存目录 3.首先在外网的环境下下载nginx的rpm包 4.目录打包tar包拷贝到离线路径 5.安装nginx 6.启动 nginx 7.停止 nginx 8.重新加载 nginx 配置 9.重新启动 nginx&#xff08;先停止再启动 nginx&#xff09; 10.检查 nginx 服务…

【分布式系统】Zookeeper学习笔记

基本概念 Zookeeper工作机制 从设计模式角度理解: 是一个基于观察者模式设计的分布式服务管理框架; 负责存储和管理大家都关心的数据, 一旦这些数据的状态发生变化, Zookeeper就将负责通知已经在Zookeeper上注册的那些观察值做出相应的反应. Zookeeper特点 Zookeeper有: 一…

EdgeOne 边缘函数 + Hono.js + Fauna 搭建个人博客

一、背景 虽然 “博客” 已经是很多很多年前流行的东西了&#xff0c;但是时至今日&#xff0c;仍然有一部分人在维护自己的博客站点&#xff0c;输出不少高质量的文章。 我使用过几种博客托管平台或静态博客生成框架&#xff0c;前段时间使用Hono.jsFauna &#xff0c;基于 …

RK3568平台开发系列讲解(I2C篇)利用逻辑分析仪进行I2C总线的全面分析

🚀返回专栏总目录 文章目录 1. 基础协议1.1. 协议简介1.2. 物理信号1.3. 总线连接沉淀、分享、成长,让自己和他人都能有所收获!😄 1. 基础协议 1.1. 协议简介 IIC-BUS(Inter-IntegratedCircuit Bus)最早是由PHilip半导体(现在被NXP收购)于1982年开发。 主要是用来方…

将深度相机的实时三维坐标数据保存为excel文档(Python+Pyrealsense2+YOLOv8)

一、如何将数据保存为excel文档 1.excel文件库与相关使用 &#xff08;1&#xff09;导入相应的excel文件库&#xff0c;导入前先要进行pip安装&#xff0c;pip install xlwt import xlwt # 导入用于创建和写入Excel文件的库 (2) 建立一个excel文档&#xff0c;并在第0行写…