不管是在属性上面还是在方法上面加了@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 类型的列表。
第三个:
- 限定符注解: 除了 @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找出来