Spring Bean 依赖注入(下)

不管是在属性上面还是在方法上面加了@Autowired注解都是根据属性的类型和名字去找bean,

set方法多个形参也是这么找

拿到所有的属性和方法的注入点后,会去调用Inject()方法

遍历每一个注入点然后进行注入

字段的注入

一开始肯定是没有缓存的,直接进到resolveFieldValue()方法,传入当前字段,当前bean对象,bean名字。

这里需要构造一个对象。【依赖描述】

👆🏻当前属性,和@Autowired注解required的值

根据传进来的对象(字段信息)去bean工厂找bean【核心方法】

方法的注入

方法里有几个参数就会有几个依赖描述器

遍历方法里的参数

方法的参数也构造一个依赖描述器

然后调用resolveDependency()【核心方法】去找到一个bean

然后再把bean放到arguments集合里(每个形参对应的bean对象都会放到这个数组里)

最终返回数组,然后执行方法

核心方法

不管传进来是什么最重要的就两个信息

字段的类型/字段的名字

方法的类型/方法的名字

初始化参数名字发现器

================================================================================

在JDK1.7之前 想要拿到方法参数的名字是比较难的 没有现成的api能用

只能拿到类型

1.8之后就能拿到参数名字 不过不是我们想要的形参名字"abc"【底层基于字节码 本地变量表】

再加个配置就可以了

=================================================================================

主流程

如果当前的字段类型或者方法参数的类型是Optional/ObjectFactory/ObjectProvider的话单独处理

如果是一般的类 就会进到else的逻辑里去

判断属性或者方法参数前有没有加上@Lazy注解

有@Lazy注解的话

调用buildLazyResolutionProxy()方法

与spring AOP有关 最后返回代理对象

如果这么写spring一开始给属性赋值是OrderService的代理对象

假如我要使用orderService的a()方法的时候它会是代理对象执行a()方法

首先其实会执行这段代码

只有当使用代理对象的a()方法的时候,也就是真正使用代理对象的时候才会根据字段信息/方法参数信息去bean工厂(容器里)去找到bean对象,再去执行对应的a()方法。这就是@Lazy注解的效果,只有当真正用到这个对象的时候才会去找对应的bean去调用方法。

如果加在方法参数上,spring一开始也是找的代理对象传给这个参数,只有在真正执行某个方法的时候才会去找bean

没有@Lazy注解的话

就会返回null,然后调用

doResolveDependency()方法【核心方法 找bean】

shortcut和缓存有关系

先把类型找出来,可能是字段的类型也可能是方法参数的类型

接下来的代码处理@Value注解

拿到@Value注解的值

情况1:如果@Value注解拿到的是String

需要先把spring的配置文件要提前引入

占位符填充底层其实是拿zhouyu这个key去Environment中找对应value【-D虚拟机配置优先级高于配置文件】

把Environment对象拿出来去匹配key

环境变量中没有找到的话就会把它当做字符串

解析spring表达式

#:会去spring容器里找zhouyu这个bean

如果spring容器里有zhouyu这个bean就不会报错了

需要类型转换器 把String类型转成OrderService类型 有的话转化成功直接return

没有就抛异常

如果没写@Value注解

只有@Autowired注解

假如我现在这么定义,然后我的容器里有两个不同名字的OrderService的bean

一个叫zhouyu,一个叫orderService

打印正常,能找到2个bean

如果属性的类型本来就就是个集合,spring就会把OrderService这个类型的所有bean找出来赋值给属性

如果是个Map,会把Map的key的类型找出来,如果key的类型不是String就返回null

强制规定

同样还会找value的类型

findAutowireCandidates()方法【核心】

根据@Autowired注解传的某个类型去找bean,返回bean的名字和对应符合这个类型的bean对象

candidateNames会返回bean的名字,也就是那些找到的匹配的那些类型

先在自己的bean工厂里面根据类型去找bean的名字,

如果还有父bean工厂还会去父bean工厂会根据类型去找bean的名字

然后把两个集合合并返回

会把所有的单例bean都找出来

某个类型对应的bean的名字

1.首先遍历BeanDefinition,根据BeanName拿到BeanDefinition

这段代码的allowEagerInit基本都是true 比较复杂先跳过不说

如果不是FactoryBean就会进到isTypeMatch()方法来

不管是不是FactoryBean都会进入到isTypeMatch()方法里,

同时这个方法还兼容了传的名字有没有带"&"符号

看name和typeToMatch的类型是否匹配

如果传的名字带“&”符号会先干掉(先跳过这里不说)

传进来beanName先看单例池有没有,如果不为空也就是不为NullBean

接下来就会判断是不是FactoryBean

在定义FactoryBean的时候不仅要实现getObject还要实现getObjectType()。

所以万一我要判断这个FactoryBean是什么类型,我不可能调用getObject去生成对象

所以我直接调用getObjectType就知道是什么类型

所以如果是FactoryBean我就直接调用getObjectType()方法就行

如果是普通bean

直接调用isInstance()方法去匹配,单例池里的对象是不是我这个类型

如果遍历的beanName在单例池还没有这个对象

拿到对应的BeanDefinition

如果beanClass还没被加载会先加载(简而言之 拿到BeanDefinition就能判断和当前的类型是否匹配了)

找到以后就会返回有哪些bean的名字是和这个类型匹配的

接下来这个map是和spring启动有关的

spring启动就会往这个map里存东西

这个map存的是 某个类型 你对应的bean是什么

---------------------------------------------------------------------------------

spring启动

左边类型的那个bean对象是右边那个

---------------------------------------------------------------------------------

如果有一些是直接存到这个map里面来的,也要去遍历

如果map里面也有我现在要的类型,就会把bean对象加到我的集合里面来

然后还要去生成bean的名字,因为这个map里面的对象是没有beanName的

接下来继续筛选

---------

小例子

自己注入自己 那到底会注入谁?

【BeanNameAware 是一个标记型接口,它让Bean可以感知到自己在Spring容器中的名称。当一个Bean实现了 BeanNameAware 接口,Spring容器在创建该Bean的过程中会自动调用其 setBeanName 方法,传入该Bean的名称】

结果:

注入的却是userService1。

---------------------------------------------------------------------------------

在依赖注入的时候是先考虑的是别人而不是自己

不是自己才会执行addCandidateEntry()的逻辑【暂时不会先用自己】

假如匹配成功了最后就会加到result返回

假如我有3个,另外两个不是自己的都不符合

就会进入到下面的result的isEmpty的逻辑

就会把自己加到result里面来。

也就是其他的bean不能用才会用自己。

isAutowireCandidate()

先配置这么个属性

autowireCandidate = true,当前这个bean可不可以用来依赖注入

核心都是调用的这个方法

拿到BeanDefinition,然后去判断这个属性是true还是false,true才能依赖注入。

其实这里用到了责任链模式

会先判断父类是true还是false,如果父类是false就直接返回false【一个和泛型相关,一个和Qualifier相关】

泛型相关:

只有父类判断出可以依赖注入才会调用

checkGeneric方法来判断可不可以依赖注入

例子

当前定义了2个泛型,而且没把这个类定义为bean

创建StockService 定义成为一个bean

继承BaseService,指定两个泛型

OrderService只留一个bean

问题:

UserService需要依赖注入吗?

答:会,它会去找父类的注入点,加了@Autowired注解的属性

但是这里是泛型,那Spring怎么知道这个类是什么呢?

对应的字段是o但是解析出来的类型是Object

所以一开始会把所有的bean都找出来

找到一大堆

然后就来到了解决泛型的这个类

在这里进行筛选的

也就是当前每个bean都回来判断是不是可以要进行依赖注入

也就是要基于UserService 这个o才是OrderService

可以通过API来确定你的泛型是什么

可以拿到UserService你继承的父类长什么样子

再通过这个API可以拿到o和s,也就是你的父类的泛型是怎么写的

然后就能知道O对应的是OrderService,S对应的是StockService

所以这个方法就是

判断当前某个bean的类型和我当前属性的类型是否匹配,就算是泛型通过API就能知道真正的类型是什么

其实当前依赖注入是有6个判断

第一个:autowireCandidate属性是否为true【是否需要依赖注入】

第二个:是不是泛型

Spring会检查泛型类型,以确保依赖注入时泛型参数的匹配。比如,如果你有一个 List<String> 的依赖,Spring会试图注入一个带有 String 类型的列表。

第三个:

  1. 限定符注解: 除了 @Primary 外,其他诸如 @Qualifier 注解可以用来指定注入特定的Bean。@Qualifier 注解可以和特定的Bean名称或自定义限定符一起使用。

第四个:Primary

第五个:优先级

第六个:beanName

Qualifier例子:

属性注入写个a

所以真正注入的Bean就是哪个限定符上写了"a"的那个bean

使用例子:

自定义注解Random写了个限定符random

再写一个roundRobin的注解

再写一个负载均衡的接口

两个不同的实现类

1.

2.

效果演示:

通过注解就能很灵活的切换你想用的负载均衡策略是哪个

原理:

其实不管加哪个注解,就相当于在实现类上加了@Qualifier注解

相当于这样的效果

先根据类型找到bean,然后再根据限定符找到对应的bean

当前属性上的注解和我当前遍历到的BeanDefinition的Qualifier注解内容是否相同

相同就表示匹配

找到后返回

如果是个集合也会调用这个方法,把所有OrderService的bean找出来

也是存成一个map,把Map的Vaules拿出来作为result返回

如果是泛型呢?所有的bean都会导入吗?

并不会 拿到的是空 null,并没有把Object拿出来

改成Object才会是所有bean

如果既不是map也不是List,就会拿当前属性的类型去找bean

不会进这里 进了也没用 这个是找集合的

会进到这个方法里

找到bean对象后

key就是beanName,value可能是bean对象也有可能是beanClass。

如果找到是空的且require是true就会抛异常

假设找到了多个bean

调用determineAutowireCandidate()方法进行过滤

当前bean的名字和属性的名字或者和方法参数的名字是否相同,相同就返回

但在名字之前还有其他的判断

=================================================================================

找到了多个bean

会先遍历判断某个bean上面有没有@Primary注解

对应BeanDefinition的isPrimary属性是不是true,是的话直接返回,多个bean加了@Primary注解就报错。

如果没加@Primary注解,就会去找优先级最高的bean

但是这里用的不是@Order注解,而是@Priority,但这个注解只能加在类的上面

数字越小优先级越高,就算没有orderService123也能正确返回bean,如果配置一样就抛异常

================================================================================

此时我只会需要1个bean对象,上面的按优先级找到以后

其他的bean对象用不到的就只会存beanClass,就没必要把bean加载出来

回到一开始,这里的Object就会有可能是存的beanClass了

如果找到的那个bean就是要注入的那个bean在这里就会把bean对象实例化

如果不是class那就直接返回

单例池里的value不会有null的情况,会用一个叫做NullBean的对象来代替,然后require为true

这里就会抛异常。【spring防止出问题 就搞了个NullBean】

假设只找到了1个bean

就会把map里唯一的元素拿出来

如果拿到的value是class

最后

都只会找到一个要注入的bean,

然后构造一个shortcutDependencyDescriptor对象(依赖描述器的子类)作为缓存。

缓存的是字段的信息,字段的类型和bean的名字。

如果第二次来注入,直接把缓存好的对象传到方法里

直接拿缓存好的beanName去得到bean对象

例子1

这里会用到缓存不?

不会,这里其实有2个注入点,其次字段和方法实现的对象都不是同一个cache标志,所以触发不了缓存。

例子2

如果把原型bean改成多例(单例的场景基本用不到 不会一个字段注入多次的)

这里调用2次就会创建2次,因为是多例

但是基于的类UserService和字段都是同一个,所以这里就会用到缓存

那为什么缓存的是beanName而不缓存bean对象呢?

如果OrderService也是原型bean呢?

那他们两个UserService得到的OrderService属性应该是不同的,如果把bean对象缓存起来,那第二次来的时候就会找错了,注入的就是同一个OrderService了。

@Resource注解【指定name也就是beanName和type】

这个注解是由CommonAnnotationBeanPostProcessor这个类来处理的

找注入点【加了@Resource的方法和属性就是注入点】,遍历正在创建的那个bean对象的时候,如果字段是static就会抛异常,如果是@Autowired注解不会抛异常,直接跳过这个注入点

最终构造出ResourceElement对象【根据字段或者方法的Member对象生成的,构造时会看有没有指定name和type 如果没指定name就用属性名或者set方法后面的名字 也就是注入点的那个beanName 如果写了type会检查和方法参数类型 或 属性的类型是否匹配】

同时构造注入点的时候还会去判断有没有lazy注解

找到注入点后调用inject方法进行注入

如果找到多个注入点,遍历调用inject方法注入,调用的其实是父类的父类【InjectElement】的inject方法,

@Autowired的Inject方法是在他们自己的类里实现的

如果lazy是true,直接构造个代理对象 赋值、注入给那个属性

核心方法就if-else

如果没指定name的值,我就会用属性名去看bean工厂有没有bean

如果没有就会调用resolveDependency()方法

会根据属性的信息(根据类型先去找)

所以@Resource会先找name再找type

fallbackToDefaultTypeMatch()======>>>>>当根据名字去bean工厂找不到bean失败的时候,进行类型的匹配

如果自己指定了名字 isDefaultName就不是true 也进入else逻辑里

如果bean工厂有这个bean就会进入到else的逻辑里

直接拿名字去getBean就完事了,如果拿不到那就报错

@Resource注解并不是Spring提供的而是java规范层面提供的

总结

@Value下面还有一步,根据类型把所有的beanName找出来

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot3+JPA+MySQL实现多数据源的读写分离(基于EntityManagerFactory)

1、简介 在Spring Boot中配置多个数据源并实现自动切换EntityManager&#xff0c;这里我编写了一个RoutingEntityManagerFactory和AOP&#xff08;面向切面编程&#xff09;的方式来实现。 这里我配置了两个数据源&#xff1a;primary和secondary&#xff0c;其中primary主数…

Redis - 高并发场景下的Redis最佳实践_翻过6座大山

文章目录 概述6座大山之_缓存雪崩 &#xff08;缓存全部失效&#xff09;缓存雪崩的两种常见场景如何应对缓存雪崩&#xff1f; 6座大山之_缓存穿透&#xff08;查询不存在的 key&#xff09;缓存穿透的原因解决方案1. 数据校验2. 缓存空值3. 频控4. 使用布隆过滤器 6座大山之_…

hash冲突四种解决办法,hash冲突除了拉链法还有什么?

1. 看hashmap 源码&#xff0c;有个问题&#xff0c;key 存放是 先hash 再与hash值的高16位值 进行异或运算。再与槽位size() 求模取余。如果多个不同的key 得出de数组位置相同。则采用链表依次存储。 2. 那么除了拉链法还有什么其他解决hash冲突的方法呢&#xff1f; a. 建立…

【学习】Web安全测试需要考虑哪些情形

一、数据加密 某些数据需要进行信息加密和过滤后才能在客户端和服务器之间进行传输&#xff0c;包括用户登录密码、信用卡信息等。例如&#xff0c;在登录某银行网站时&#xff0c;该网站必须支持SSL协议&#xff0c;通过浏览器访问该网站时&#xff0c;地址栏的http变成https…

堂哥让我给他做个真人动漫头像

背景 堂哥最喜欢的动漫是死神。他给了我一张死神主角一户的头像&#xff0c;以及自己的头像&#xff0c;希望我产出一张真人动漫头像。 一户的头像&#xff1a; 堂哥自拍照&#xff1a; 最近&#xff0c;有大佬部署了个stable diffusion&#xff0c;正好拿来一试身手。 stab…

vue项目报这个错是 Same `value` exist in the tree: 0008E3000E1A?

警告 "Same value exist in the tree: 0008E3000E1A" 表示在树形选择器中存在相同的值。这通常是由于树形选择器的数据中存在重复的值造成的。就是返回的值中&#xff0c;有俩个id相同

【Redis】数据类型、事务执行、内存淘汰策略

目录 数据类型 Redis事务执行步骤 步骤&#xff1a; redis内存淘汰策略 设置内存淘汰策略 1.设置配置文件 2.通过命令设置 数据类型 官网解释 Understand Redis data types | Redis 首先&#xff0c;Redis 的所有键都是字符串,常用的数据类型有 5 种&#xff1a;Strin…

快速上手 Elasticsearch:Docker Compose 部署详解

最近面试竞争日益激烈&#xff0c;Elasticsearch作为一款广泛应用的中间件&#xff0c;几乎成为面试中必考的知识点。最近&#xff0c;AIGC也备受关注&#xff0c;而好多的AI项目中也采用了Elasticsearch作为向量数据库&#xff0c;因此我们迫切希望学习Elasticsearch。对于学习…

【机器学习】基于变色龙算法优化的BP神经网络分类预测(SSA-BP)

目录 1.原理与思路2.设计与实现3.结果预测4.代码获取 1.原理与思路 【智能算法应用】智能算法优化BP神经网络思路【智能算法】变色龙优化算法&#xff08;CSA)原理及实现 2.设计与实现 数据集&#xff1a; 数据集样本总数2000 多输入多输出&#xff1a;样本特征24&#xff…

工业4.0 底层逻辑

许多场合下&#xff0c;工业4.0 的概念已经被滥用了&#xff0c;它与物联网&#xff0c;工业物联网等概念被滥用一样&#xff0c;几乎什么都往里面装。演变成了一句口号和愿景。许多人并不清楚工业4.0的底层逻辑到底是什么&#xff1f;如何遵循工业4.0 的思想构建新一代智能制造…

社交媒体行业巨头:揭示Facebook的市场地位

引言 随着数字化时代的蓬勃发展&#xff0c;社交媒体已经深刻改变了人们的生活方式和社会交往方式&#xff0c;而Facebook作为其中的领军者&#xff0c;扮演着举足轻重的角色。本文将深入探讨Facebook在社交媒体行业中的市场地位&#xff0c;从用户规模、收入来源、技术创新、…

【Android】美团组件化路由框架WMRouter源码解析

前言 Android无论App开发还是SDK开发&#xff0c;都绕不开组件化&#xff0c;组件化要解决的最大的问题就是组件之间的通信&#xff0c;即路由框架。国内使用最多的两个路由框架一个是阿里的ARouter&#xff0c;另一个是美团的WMRouter。这两个路由框架功能都很强大&#xff0…

智能运维的发展演进

Gartner在2018年提出AIOps&#xff08;Artificial Intelligence for IT Operations&#xff09;&#xff0c;即人工智能在IT运维领域的应用。智能运维在技术方案、平台、场景都更加聚焦&#xff0c;恰逢AI技术飞速发展。用户可以实时监控分析大量的运维数据&#xff0c;预防和防…

python + tensorflow 开局托儿所自动点击脚本

python开局托儿所自动点击脚本 屏幕截图图片数字识别消除算法自动点击 屏幕截图 python 屏幕截图可以使用pyautogui或者PIL。我使用的是PIL中的ImageGrab(要授权)。 image ImageGrab.grab(bbox(0, 0, tool.static_window_width, tool.static_window_height)) image np.arra…

ModbusRTU/TCP/profinet网关在西门子博图软件中无法连接PLC的解决方法

ModbusRTU/TCP/profinet网关在西门子博图软件中无法连接PLC的解决方法 在工业生产现场&#xff0c;ModbusRTU/TCP/profinet网关在与西门子PLC连接时&#xff0c;必须要使用西门子的博图软件来进行配置&#xff0c;博图v17是一个集成软件平台&#xff0c;专业版支持300、400、12…

海外基金牌照的优势及注意事项-华媒舍

一、了解海外基金牌照 在投资领域&#xff0c;海外基金牌照是指投资者可以通过获得海外金融监管机构颁发的许可证&#xff0c;参与海外基金投资。拥有海外基金牌照的投资者可以享受更广泛的投资机会&#xff0c;包括跨境投资、全球资产配置等。 二、海外基金牌照的优势 多元化…

Unity 学习日记 8.2D物理引擎

1.2D刚体的属性和方法 2.碰撞器

还在购买蜘蛛池做SEO?有用吗?

蜘蛛池是什么&#xff1f;租用蜘蛛池对SEO优化到底有没有用&#xff1f;网上很多说法&#xff0c;且各执一词&#xff0c;那些出租蜘蛛池的写的软文不算。站长帮一直本着负责任的态度&#xff0c;从客观的角度&#xff0c;来为大家一一解惑。 本文 虚良SEO 原创&#xff0c;转载…

如何查询网贷大数据信用报告?哪个查询平台更好?

在互联网金融迅速发展的当下&#xff0c;网贷大数据查询平台已成为许多人在申请贷款前的重要工具。然而&#xff0c;随着这些平台的广泛使用&#xff0c;安全问题日益凸显&#xff0c;许多用户反映自己的个人信息在查询过程中被泄露。为了应对这一挑战&#xff0c;本文将探讨如…

fiddler配合夜神模拟器对APP进行抓包

fiddler 配置 设置https Tools – -> Options —> HTTPS 在这里插入图片描述 下载证书&#xff0c;并安装 修改模拟器网络连接 cmd 查看本机本地IP点击模拟器wifi, 长按修改为手动配置&#xff1a; IP 8888使用浏览器&#xff0c;访问IP 8888 下载证书 。点击Fiddler…