Spring源码学习笔记7——Spring bean的初始化
一丶前言#
上篇中我们了解了Spring bean的实例化——存在方法覆盖的使用CGLIB动态代理生成子类,反之反射调用构造函数。实例化后bean中的字段都是默认值,接下来就是对bean的属性进行填充,并且还会调用一些生命周期相关的方法
二丶源码学习的简单例子#
三丶属性注入#
属性注入的操作集中再populateBean方法中
1.前置检查#
如果没有需要注入的值那么直接跳过,mybatis中的mapper一般情况下都会跳过,
2.InstantiationAwareBeanPostProcessors # postProcessAfterInstantiation#
调用所有InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation方法,InstantiationAwareBeanPostProcessor实例化感知接口,postProcessAfterInstantiation入参有bean对象和bean的名称,可以在这里面进行自定义的属性注入,如果返回false表示后续不需要spring帮我们进行依赖注入,反之需要spring帮我们进行依赖注入
3.属性注入#
3.1applyPropertyValues#
属性注入发生在applyPropertyValues方法中
Spring构建一个BeanDefinitionValueResolver来解析属性,BeanDefinitionValueResolver持有当前BeanFactory,bean的定义和类型转换器,在其resolveValueIfNecessary方法中定义了许多不同类型的解析方法
-
解析RuntimeBeanReference类型
-
根据类型解析
如果存在多个符合要求的bean(bean必须是依赖注入的候选者,且类型符合),根据符合要求bean的Primary信息觉得使用哪个bean,如果还是无法判断,那么根据优先级选择,后续回到父BeanFactory(要求父BeanFactory是AutowireCapableBeanFactory类型)中找符合要求的bean
-
根据名称解析bean
会先对bean名称进行spel的解析,然后从工厂中找对应名称的bean,当然也会去父工厂中找
-
注册依赖关系
beanA需要beanB进行属性注入,那么会
-
-
解析TypeStringValue类型
解析spel表达式的值,并且进行类型转换
-
反射设置属性
3.2 @Resource,@Autowired,@Value注解是如何生效的#
这里需要了解两个接口
MergedBeanDefinitionPostProcessor和 InstantiationAwareBeanPostProcessor
@Resource,@Autowired,@Value的实现依赖于这两个接口,首先在MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition,会去扫描class字段or方法上面的注解保存在map缓存中,后InstantiationAwareBeanPostProcessor#postProcessPropertyValues在和依赖之前的扫描到结果进行依赖注入
实现了MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法,此方法在实例化bean之后会回调,
InstantiationAwareBeanPostProcessor#postProcessProperties在populateBean方法中会遍历所有的InstantiationAwareBeanPostProcessor方法,调用postProcessProperties方法从而进行依赖注入
3.2.1@Resouce注解实现的原理#
这两个注解发挥作用依赖于 CommonAnnotationBeanPostProcessor,
CommonAnnotationBeanPostProcessor在这个方法中会反射找该类中@Resouce注解标注的字段or方法
以上两步都会循环处理父类,直到父类是Object
-
ResourceElement的构造方法
-
@Resource注解name处理
如果字段上的注解没有指定name,默认使用字段名称作为资源名称,如果是方法上面没有标注注解,如果是一个set方法(setABC),那么资源名称是aBC,如果不是set方法那么资源名称是方法名称
-
支持占位符的解析
-
资源类型@Resouce的type
如果指定了type 会确认字段类型 or方法第一个参数类型是否和指定类型匹配,指定类型必须是字段or方法第一个参数类型的子类或者就是字段or方法第一个参数的类型,如果没有指定类型,那么类型是字段对应的类型,or方法第一个参数的类型
-
还支持搭配Lazy注解实现懒注入?
-
-
依赖注入
如果是字段那么反射根据设置字段值,如果是方法反射调用方法,其中getResouceToInject会从容器中获取需要的bean
这里有个细节的点,很多博客上面都说
我本人尝试却不是这样的,其实这里根本没有根据类型,根据名称。如下例子
但是这样下面这样又是可以成功注入的
为什么昵
也就是说如果我们没有指定@Resouce的name,且字段的名称,or set方法去除set后第一个字母小写,非set方法就是方法名称,在BeanFactory中不存在那么会根据类型去找,
如果指定了name,或者包含且字段的名称,or set方法去除set后第一个字母小写,非set方法就是方法名称在BeanFactory中存在,会按照名称找
3.2.1.@Autowired 和 @Value 注解的原理#
这两个注解功能的实现依赖于AutowiredAnnotationBeanPostProcessor,大流程和CommonAnnotationBeanPostProcessor是一样的
3.2.1.1@Autowired#
原理和@Resouce如出一辙
-
postProcessMergedBeanDefinition方法扫描注解保存到map缓存
具体扫描逻辑在buildAutowiringMetadata方法中
-
依赖注入
- 字段注入
最终调用的是BeanFactory的resolveDependency方法,然后调用doResolveDependency方法,后续找候选的bean调用findAutowireCandidates
如果存在多个符合要求的bean候选者,通过determineAutowireCandidate方法,@primary标注的bean将胜出,如果都没有标注,那么比较优先级javax.annotation.Priority中的值,如果还是不行再根据字段名称or方法参数名称
3.2.1.2 @Value#
@value注解的流程和@Autowired是一样的,但是在doResolveDependency方法的时候
ContextAnnotationAutowireCandidateResolver #getSuggestedValue 会解析Value注解给出建议的值
3.3@Qualifier如何生效#
在resolveDependency调用doResolveDependency方法,然后调用 findAutowireCandidates 其中会调用isAutowireCandidate判断当前的bean是否是候选bean,会使用QualifierAnnotationAutowireCandidateResolver中的isAutowireCandidate方法
来判断是否是合格的候选者,可以看到在方法注入的时候,也可以标注在参数上来指定bean名称
3.4总结@Resouce @Autowried的异同#
-
相同
- 都可以标注在方法上和字段上
- 都可以使用@Qualifier 限定候选者
- 都可以进行依赖注入
- 都是通过 MergedBeanDefinitionPostProcessor和InstantiationAwareBeanPostProcessor的回调实现依赖注入的
- 底层原理都是反射,反射设置字段的值,反射调用方法
- 二者都是在实例化bean之后,调用MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition方法扫描需要注入的字段 和 方法 放入到缓存中,后
- 依赖注入都是发生在InstantiationAwareBeanPostProcessor#postProcessPropertyValues的方法中
- 依赖注入存在多个符合条件的bean都是通过@Primary 的bean胜出(实际上调用BeanDefinition中的isPrimary()),如果都isPrimary 不能决出胜负则比较优先级,如果还是存在多个那么抛出异常
- 二者都在找不到合适的bean的时候抛出异常
-
不同
-
@Resouce 是基于CommonAnnotationBeanPostProcessor 实现的
-
@Autowried 是基于AutowiredAnnotationBeanPostProcessor 实现的
-
@Resouce注解默认是根据bean的名称去注入,如果没有指定name,会将字段的名称作为bean的名称,set方法会去掉set前缀且第一个字母小写作为bean的名称,非set方法 方法名称就是bean的名称,如果指定了name 就以name中的内容作为bean的名称
如果没有指定name,使用默认的name,且默认的name在容器中存在,那么调用resolveByBeanName获取bean,要求类型必须兼容字段声明类型or方法入参类型
如果没有指定name且name不存在容器中,会根据类型注入
-
@Autowried 默认根据类型注入,比如Service存在实现类A和B,字段or方法声明类型为AService但是字段or参数名称为b,还是会根据类型找到符合的bean名称找到a,而不会因为名称是b选择B注入,如果字段or方法参数类型是Service,但是字段or参数名称为a,且没有标注@Qualifier 那么会找到a和b作为候选者,但是后面比较Primary,再比较javax.annotation.Priority注解,然后再根据字段的名称or参数的名称去筛选
-
@Autowried 标注在方法上支持 0个参数 和任意多个参数 但是@Resouce 要求必须有一个参数
-
@Resouce 优先根据bean名称注入的前提是 指定了name or 没有指定name但是字段名称,set方法去除set前缀第一个字母小写的名称or方法名称的对应的bean 存在于容器中
如果不满足根据名称注入的前提,也就是没有指定name,名称指示的bean不在容器中,会根据类型来注入,这个时候逻辑就和@Autowried 一样
那么根据bean的类型注入存在多个bean的时候判断的优先级是什么(@Resouce or @Autowried根据类型注入都是这个优先级)
1.BeanDefinition的isPrimary 返回true (一般是通过注解orxml 配置设置为true)
2.比较优先级,注意这里的优先级不是@Order,Ordered,@PriorityOrdered可以左右的,而是要javax.annotation.Priority 注解才能左右
3.根据字段名称,or方法参数名称 来决定使用哪个bean
@Qualifier 是在根据类型筛选Bean的得到多个bean之后调用 isAutowireCandidate 中判断候选者的名称是否符合的时候生效的,是优先于根据字段名称,or方法参数名称 的,根据字段名称,or方法参数名称 是最后无法决定才会使用的策略,@Qualifier是找符合类型的bean后进行的过滤
四丶初始化bean#
在populateBean 完成bean的依赖注入之后,会进行bean的初始化
一共有四步,回调Aware接口,回调 BeanPostProcessor 的postProcessBeforeInitialization方法,执行初始化方法,回调BeanPostProcessor的postProcessAfterInitialization方法
1.回调Aware接口#
你可能会想那ApplicationContextAware昵,EnvironmentAware昵,拜托,这是在BeanFactory里面耶,没什么要调用和BeanFactory 无关的接口啊,所有它们何时被调用,请接着看下去
2.回调 BeanPostProcessor 的postProcessBeforeInitialization#
2.1 ApplicationContextAwareProcessor#
这个类负责回调一些Aware接口(和BeanFactory无关的Aware)
2.2InitDestroyAnnotationBeanPostProcessor#
这个类负责实现@PostConstruct和@PreDestroy 注解标注的方法,这个类被CommonAnnotationBeanPostProcessor 实现
具体原理和@Resouce一样,首先都是postProcessMergedBeanDefinition方法扫描注解(bean实例化后该方法被回调)
找生命周期相关的注解
然后再postProcessBeforeInitialization 回调的时候会执行相关的@PostConstruct标注的方法
注意那怕方法需要参数,spring 也不会管,直接参数就是null,
同理bean被销毁的时候回调对应的方法postProcessBeforeDestruction 进行@PreDestory标注方法的回调
2.3ServletContextAwareProcessor#
负责回调ServletContextAware 和 ServletConfigAware 类型对象对应的方法
3.执行初始化方法#
这里的用户自定义初始化方法 可以通过@Bean(initMethod = "initMethod") 和xml配置中的Init-method指定,具体逻辑就是反射拿到方法,然后反射执行
4.回调BeanPostProcessor的postProcessAfterInitialization#
这里是Aop 实现的重点,可以在这里返回代理对象实现动态代理,最终是动态代理的对象放入到了容器中, 后续会进行详细的学习
4.1ApplicationListenerDetector#
这里实现了监听器的注册 ApplicationListenerDetector 会判断如果当前bean实现了ApplicationListener 那么会进行注册,这个之前笔记讲到过
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~