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找出来

相关推荐

  1. .NET8 依赖注入

    2024-03-21 18:24:01       35 阅读
  2. C# DI依赖注入

    2024-03-21 18:24:01       37 阅读
  3. 【csharp】依赖注入

    2024-03-21 18:24:01       33 阅读
  4. SpringBoot整理-依赖注入

    2024-03-21 18:24:01       29 阅读
  5. WebSocketServer依赖注入问题

    2024-03-21 18:24:01       29 阅读
  6. Spring 依赖注入

    2024-03-21 18:24:01       14 阅读
  7. golang wire 依赖注入

    2024-03-21 18:24:01       11 阅读
  8. SpringBoot Mockito 依赖注入

    2024-03-21 18:24:01       10 阅读
  9. Spring的依赖注入

    2024-03-21 18:24:01       12 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-21 18:24:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-21 18:24:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-21 18:24:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-21 18:24:01       20 阅读

热门阅读

  1. python之列表遍历

    2024-03-21 18:24:01       20 阅读
  2. leetcode-键盘行

    2024-03-21 18:24:01       23 阅读
  3. 【力扣】383.赎金信

    2024-03-21 18:24:01       18 阅读
  4. 月份选择,多选,数组去重

    2024-03-21 18:24:01       17 阅读
  5. C语言例3-30:位逻辑运算的应用例子

    2024-03-21 18:24:01       19 阅读
  6. 深度学习相关记录《一》

    2024-03-21 18:24:01       19 阅读
  7. 云备份与云存储有什么不同?有什么需要注意的

    2024-03-21 18:24:01       21 阅读
  8. ChatGPT都能做什么,有哪些场景?

    2024-03-21 18:24:01       19 阅读
  9. NFS(网络文件系统)介绍与实践

    2024-03-21 18:24:01       16 阅读
  10. 数据库迁移测试

    2024-03-21 18:24:01       20 阅读
  11. HJ3 明明的随机数

    2024-03-21 18:24:01       15 阅读