一丶前言

上篇中我们了解了Spring bean的实例化——存在方法覆盖的使用CGLIB动态代理生成子类,反之反射调用构造函数。实例化后bean中的字段都是默认值,接下来就是对bean的属性进行填充,并且还会调用一些生命周期相关的方法

二丶源码学习的简单例子

  • 基于xml配置

三丶属性注入

属性注入的操作集中再populateBean方法中

1.前置检查

如果没有需要注入的值那么直接跳过,mybatis中的mapper一般情况下都会跳过,

  • xml配置和PropertyValues举例

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方法

  • 标注@Resouce注解的字段

    会获取类中定义的字段,如果是静态字段抛出异常,如果是需要忽略的类型那么不做处理(ignoredResourceTypes 可以进行配置)

  • 标注@Resouce的方法

以上两步都会循环处理父类,直到父类是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方法中

    1. 扫描字段

    2. 扫描方法

  • 依赖注入

    • 字段注入

    最终调用的是BeanFactory的resolveDependency方法,然后调用doResolveDependency方法,后续找候选的bean调用findAutowireCandidates

    如果存在多个符合要求的bean候选者,通过determineAutowireCandidate方法,@primary标注的bean将胜出,如果都没有标注,那么比较优先级javax.annotation.Priority中的值,如果还是不行再根据字段名称or方法参数名称

    • 方法注入

      找bean候选者的过程和字段注入相同,但是和@Resouce不同的是,@Autowird支持多个参数和0个参数,多个参数都会调用resolveDependency方法找合适的候选者,0个参数还是会反射调用方法,入参是空数组

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的异同

  1. 相同

    1. 都可以标注在方法上和字段上
    2. 都可以使用@Qualifier 限定候选者
    3. 都可以进行依赖注入
    4. 都是通过 MergedBeanDefinitionPostProcessor和InstantiationAwareBeanPostProcessor的回调实现依赖注入的
    5. 底层原理都是反射,反射设置字段的值,反射调用方法
    6. 二者都是在实例化bean之后,调用MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition方法扫描需要注入的字段 和 方法 放入到缓存中,后
    7. 依赖注入都是发生在InstantiationAwareBeanPostProcessor#postProcessPropertyValues的方法中
    8. 依赖注入存在多个符合条件的bean都是通过@Primary 的bean胜出(实际上调用BeanDefinition中的isPrimary()),如果都isPrimary 不能决出胜负则比较优先级,如果还是存在多个那么抛出异常
    9. 二者都在找不到合适的bean的时候抛出异常
  2. 不同

    1. @Resouce 是基于CommonAnnotationBeanPostProcessor 实现的

    2. @Autowried 是基于AutowiredAnnotationBeanPostProcessor 实现的

    3. @Resouce注解默认是根据bean的名称去注入,如果没有指定name,会将字段的名称作为bean的名称,set方法会去掉set前缀且第一个字母小写作为bean的名称,非set方法 方法名称就是bean的名称,如果指定了name 就以name中的内容作为bean的名称

      如果没有指定name,使用默认的name,且默认的name在容器中存在,那么调用resolveByBeanName获取bean,要求类型必须兼容字段声明类型or方法入参类型

      如果没有指定name且name不存在容器中,会根据类型注入

    4. @Autowried 默认根据类型注入,比如Service存在实现类A和B,字段or方法声明类型为AService但是字段or参数名称为b,还是会根据类型找到符合的bean名称找到a,而不会因为名称是b选择B注入,如果字段or方法参数类型是Service,但是字段or参数名称为a,且没有标注@Qualifier 那么会找到a和b作为候选者,但是后面比较Primary,再比较javax.annotation.Priority注解,然后再根据字段的名称or参数的名称去筛选

    5. @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 那么会进行注册,这个之前笔记讲到过

Spring源码学习笔记7——Spring bean的初始化的更多相关文章

  1. Spring 源码学习笔记10——Spring AOP

    Spring 源码学习笔记10--Spring AOP 参考书籍<Spring技术内幕>Spring AOP的实现章节 书有点老,但是里面一些概念还是总结比较到位 源码基于Spring-a ...

  2. Spring 源码学习笔记11——Spring事务

    Spring 源码学习笔记11--Spring事务 Spring事务是基于Spring Aop的扩展 AOP的知识参见<Spring 源码学习笔记10--Spring AOP> 图片参考了 ...

  3. Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点

    Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...

  4. Spring源码学习笔记9——构造器注入及其循环依赖

    Spring源码学习笔记9--构造器注入及其循环依赖 一丶前言 前面我们分析了spring基于字段的和基于set方法注入的原理,但是没有分析第二常用的注入方式(构造器注入)(第一常用字段注入),并且在 ...

  5. Spring源码学习-容器BeanFactory(五) Bean的创建-探寻Bean的新生之路

    写在前面 上面四篇文章讲了Spring是如何将配置文件一步一步转化为BeanDefinition的整个流程,下面就到了正式创建Bean对象实例的环节了,我们一起继续学习吧. 2.初始化Bean对象实例 ...

  6. spring源码学习笔记之容器的基本实现(一)

    前言 最近学习了<<Spring源码深度解析>>受益匪浅,本博客是对学习内容的一个总结.分享,方便日后自己复习或与一同学习的小伙伴一起探讨之用. 建议与源码配合使用,效果更嘉, ...

  7. Spring源码学习笔记之bean标签属性介绍及作用

    传统的Spring项目, xml 配置bean在代码中是经常遇到, 那么在配置bean的时候,这些属性的作用是什么呢? 虽然说现在boot项目兴起,基于xml配置的少了很多, 但是如果能够了解这些标签 ...

  8. Spring源码学习笔记之基于ClassPathXmlApplicationContext进行bean标签解析

    bean 标签在spring的配置文件中, 是非常重要的一个标签, 即便现在boot项目比较流行, 但是还是有必要理解bean标签的解析流程,有助于我们进行 基于注解配置, 也知道各个标签的作用,以及 ...

  9. spring源码阅读笔记06:bean加载之准备创建bean

    上文中我们学习了bean加载的整个过程,我们知道从spring容器中获取单例bean时会先从缓存尝试获取,如果缓存中不存在已经加载的单例bean就需要从头开始bean的创建,而bean的创建过程是非常 ...

  10. Spring 源码学习(4)—— bean的加载part 1

    前面随笔中,结束了对配置文件的解析工作,以及将配置文件转换成对应的BeanDefinition存储在容器中.接下来就该进行bean的加载了. public Object getBean(String ...

随机推荐

  1. destoon上做纯js实现html指定页面导出word

    因为最近做了范文网站需要,所以要下载为word文档,如果php进行处理,很吃后台服务器,所以想用前端进行实现.查询github发现,确实有这方面的插件. js导出word文档所需要的两个插件: 1 2 ...

  2. Termius for macOS or Windows Download ssh

    mac电脑   苹果系统 window 系统 链接  服务器,访问 ecs ,ssh链接 工具 下载地址:http://pgyd.online/?website/53.html Termius for ...

  3. 4款.NET开源的Redis客户端驱动库

    前言 今天给大家推荐4款.NET开源免费的Redis客户端驱动库(以下排名不分先后). Redis是什么? Redis全称是REmote DIctionary Service,即远程字典服务.Redi ...

  4. Python:基础&爬虫

    Python:基础&爬虫 Python爬虫学习(网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另 ...

  5. JAVA架构师

    https://github.com/zq99299/note-architect https://zq99299.github.io/note-architect/hc/ https://zq992 ...

  6. 批处理(Batch或离线计算)和流计算(Streaming或实时计算)

    大数据处理流程 课程:https://developer.aliyun.com/learning/course/432/detail/5385 流程 发 批处理(Batch或离线计算) 基础:goog ...

  7. 聊聊BIO、NIO与AIO的区别(转)

    转自:https://www.cnblogs.com/blackjoyful/p/11534985.html 题目:说一下BIO/AIO/NIO 有什么区别?及异步模式的用途和意义? BIO:Apac ...

  8. Util应用框架基础(五) - 异常处理

    本节介绍Util应用框架如何处理系统错误. 概述 系统在运行过程中可能发生错误. 系统错误可以简单分为两类: 系统异常 系统本身出现的错误. 业务异常 不满足业务规则出现的错误. 如何处理系统异常 如 ...

  9. 如何将word格式的文档转换成markdown格式的文档

    如何将word格式的文档转换成markdown格式的文档 如何将word格式的文档转换成markdown格式的文档 前言 A. 介绍Markdown和Word格式文档 什么是Markdown? Mar ...

  10. 使用DocumentBuilderFactory解析XML浅谈

    背景: 当使用Java解析XML时,可以使用javax.xml.parsers.DocumentBuilderFactory类.这个类提供了一种创建解析XML的文档对象的方式.DocumentBuil ...