在容器启动快完成时, 会把所有的单例 bean 进行实例化, 也可以叫做预先实例化.
这样做的好处之一是, 可以及早地发现问题, 及早的抛出异常, 及早地解决掉.
本文就来看下整个的实例化过程. 其实还是比较繁琐的.
一, 从容器中找出所有的 bean 定义名称
因为不知道谁是单例 bean, 所以只能先全部找出来. 如下图 01:
二, 循环遍历所有的 bean 名称, 检查是否符合条件
首先要合并 bean 定义, 因为 bean 定义可以有父子关系, 类似继承.
然后这个合并后的 bean 定义必须是, 非抽象的, 单例的, 非延迟初始化的.
那么它就满足条件, 如下图 02:
三, 判断是否为 FactoryBean<?> 类型
如果不是的话, 说明该 beanName 对应一个普通的 bean, 可以直接实例化.
如果是的话, 说明该 beanName 对应的是一个工厂, 这个工厂本身是单例的.
但是它里面生产的 bean 不一定是单例的. 即使是的话, 还要判断是否要积极的去初始化工厂里的 bean.
具体的判断如下图 03:
编程新说注: 提到 FactoryBean<?> 类型, 是否想起 & 符号的作用呢?
四, 开始进入众所熟知的 getBean(String name) 方法
在上一图中可以看到 Spring 对 bean 的实例化时竟然是调用的 getBean(..) 方法.
这样共用一套代码, 简单省事. 不仅如此, 当获取一个 bean 的依赖时, 也可以用该方法.
这样 getBean(..) 就是一个综合方法, 没有 bean 实例就生成, 有的话就直接返回.
如下图 04:
五, 对手工直接注册的单例对象进行检测
bean 实例除了可以用 bean 定义生成外, 还可以由开发人员直接注册一个 bean 实例.
这样在使用 bean 定义生成实例前, 先使用 beanName 去手动注册的 bean 实例集合中找一下.
如下图 05:
如果找到了, 就不用生成了, 否则就会根据 bean 定义生成 bean 实例.
六, 对 FactoryBean<?> 类型的检测
这和上面提到的是一个类型, 它是一个工厂, 可以认为是包裹在了实际 bean 实例的外面.
这样可以有一些特殊的作用, 不好之处就是每次都要检测下, 然后从它内部拿出实际的 bean 实例.
具体检测过程不再展开, 如下图 06:
七, 对类型进行转换, 如果有必要的话
上面我们仅仅是用 beanName 去手动注册的实例集合中寻找, 万一这个手动注册的实例类型和 bean 定义要求的不兼容呢?
因此要进行类型检测与转换, 实在不行就抛异常, 如下图 07:
如果成功的话, 就表明手动注册的 bean 定义实例满足要求, 将它返回即可.
编程新说注: 如果在第五步没有找到 beanName 对应的手动注册的 bean 实例, 那开始根据 bean 定义来生成 bean 实例. 继续往下看.
八, 准备好显式指定的依赖, 如 @DependsOn 指定的
先获取合并后的 bean 定义, 然后从中读出显式指定的依赖, 并逐个处理.
使用 registerDependentBean(..) 将依赖关系写入容器, 由容器维护.
并同样使用 getBean(..) 方法实例化这些依赖, 一模一样的套路.
其实就是递归, 如下图 08:
编程新说注: 接下来使用 createBean(..) 方法正式开始创建 bean
九, 解析出 bean 的 Class<?>
因为在注册 bean 定义时并不一定加载类, 可能只是一个字符串的类名称.
所以要根据类名称去加载类, 并得到类的 Class<?>. 如下图 09:
十, 调用 bean 后处理器的 postProcessBeforeInstantiation 方法
此时还处在实例化之前, 让用户有机会来提供一个 bean 实例或代理.
这样 Spring 就不再进行后续的实例化步骤, 直接返回这个用户提供的.
如果用户没有提供的话, Spring 继续后续的处理. 如下图 1011:
十一, 调用 InstanceSupplier 生成 bean 实例, 如果有的话
在注册 bean 定义时, 可以设置一个 Supplier<?> 类型的函数式接口.
其实就是用户可以提供一段创建 bean 实例的代码, 这样 Spring 就使用它来创建 bean 实例.
然后将这个实例返回即可, 如下图 12:
十二, 通过 FactoryMethod 来生成 bean 实例, 如果 FactoryMethodName 不为 null 的话
如下图 13:
FactoryMethod 就是工厂方法, 说明 bean 的实例是通过调用这个工厂方法返回的, 而不是通过反射调用构造函数返回的.
工厂方法有两种, 静态的和实例的. 如果是实例的, 那还要有一个 FactoryBeanName 来指定一个 bean 名称, 根据它可以从容器中获取一个对象, 用作工厂.
如果是静态的, 那就不需要实例了, 直接把 bean 定义中的类型作为工厂类即可. 如下图 14:
然后根据工厂方法的名称, 从 bean 定义中解析出对应的 Method 对象. 然后再解析出构造方法参数用作工厂方法的参数.
最终通过反射调用这个工厂方法, 获取返回值, 就是 bean 实例了, 如下图 15:
这个 bean 实例会用一个 BeanWrapper 接口进行包装, 这个接口提供一些基础的 JavaBean 功能, 如数据的类型转换然后再进行属性绑定等.
十三, 调用 bean 后处理器的 determineCandidateConstructors 方法来确定候选构造方法
如下图 16:
这里涉及到从多个候选构造方法中选出一个最合适的, 是一个比较复杂的过程.
最后也是通过反射调用构造方法, 获取到 bean 的实例. 如下图 17:
然后也用 BeanWrapper 接口进行包装.
十四, 使用更适合的构造方法来实例化, 如果有的话
如果上一步没有执行的话, 则使用 bean 定义中更适合的构造方法, 如下图 18:
十五, 使用默认无参的构造方法来实例化
如果上一步没有执行的话, 则使用默认无参构造方法, 如下图 19:
编程新说注: 至此 bean 实例已经创建好了.
十六, 应用 bean 后处理器的 postProcessMergedBeanDefinition 方法
上两篇文章详细介绍了 bean 后处理器, 主要是用来实现注解的功能的.
如下图 2021:
十七, 此时就可以暴露早期的 bean 引用了, 如果需要的话
如允许循环引用的话, 就需要这个操作, 如下图 22:
十八, 应用 bean 后处理器的 postProcessAfterInstantiation 方法
如下图 23:
且该方法如果返回 false, 该 bean 实例后续的 bean 后处理器操作将不再执行.
十九, 根据设置的自动装配类型处理自动装配问题
如下图 24:
如果配置的是按名称自动装配, 则会把所有 setter 方法中参数类型是非基本类型的都找出来.
然后按照属性名称从容器中找出同名的 bean, 作为属性值保存起来以备后用. 如下图 25:
如果配置的是按类型自动装配, 则会把所有 setter 方法中参数类型是非基本类型的都找出来.
然后按照属性类型从容器中解析出对应的 bean, 作为属性值保存起来以备后用. 如下图 26:
如果看了上两篇文章, 会发现这里按类型从容器中解析 bean 的套路和上两篇一模一样.
编程新说注:
以上的 setter 方法上不需要标任何注解, 因为显式设置了自动装配类型.
而默认情况其实是没有设置的, 即 AUTOWIRE_NO, 所以我们要标上 @Autowired 注解.
二十, 应用 bean 后处理器的 postProcessProperties 方法
在这一步其实是完成了依赖的注入, 如下图 27:
二十一, 其余属性值到 bean 属性的绑定
这一步是由 BeanWrapper 这个接口完成的, 如下图 28:
编程新说注: 至此 bean 的所有依赖装配和属性设置都已完毕.
二十二, 应用 bean 后处理器的 postProcessBeforeInitialization 方法
这一步就开始执行初始化方法了. 如下图 2930:
二十三, 执行 bean 的初始化方法 afterPropertiesSet()
如果 bean 实现了 InitializingBean 接口, 此刻会调用它唯一的方法.
如下图 31:
二十四, 执行 bean 定义中指定的初始化方法 initMethod
如果 bean 定义是使用 @Bean 注册的, 可以通过设置注解属性指定初始化方法.
如下图 32:
编程新说注:
之前文章中写过, 有三种方式可以指定初始化方法:
1)@PostConstruct 注解, 2)InitializingBean 接口, 3)@Bean 注解
这里有两个问题需要记住:
1) 如果两种或三种方式都指向了同一个方法, 这个方法也只会被执行一次.
2) 三种方式指定的初始化方法的执行顺序就按刚刚列出的 1,2,3 这个顺序.
二十五, 应用 bean 后处理器的 postProcessAfterInitialization 方法
如下图 3334:
编程新说注: 至此 bean 的初始化工作已经完成.
二十六, 注册 bean 销毁时要执行的代码, 如果需要的话
除了使用了之前说过的三种方式指定过销毁发方法之外, 如果 bean 实现了 AutoCloseable 接口也算.
如果使用 @Bean 注册且没有指定销毁方法, 那么默认把 close 和 shutdown 方法作为销毁方法.
这些情况都是需要注册的, 如下图 35:
编程新说注: 至此 bean 实例本身已经准好了.
二十七, 缓存单例的 bean
如果这个 bean 是单例的, 而且是新创建的, 会把它缓存到容器里, 以备后用.
如下图 36:
二十八, 进行 Scope 处理
如果一个 bean 指定了 Scope, 即它的生命周期既非单例也非原型而是属于某一个范围.
Spring 暂时支持的范围如下图 37:
实现原理其实很简单, 比如 Session 范围, 那就先从 Session 中获取, 没有的话生成一个放入 Session 中即可.
二十九, 对 FactoryBean<?> 类型的检测与处理
刚刚创建的这个 bean 可能是 FactoryBean<?> 类型, 即一个工厂. 而我们想要的可能是工厂里生成的 bean.
简单来说, 那就从工厂中把 bean 拿出来即可.
三十, 类型的检测与转换
最终得到的 bean 实例可能与期望的类型不兼容, 此时就要进行类型转换.
转换成功的话就返回, 失败的话就抛出类型不匹配异常.
至此一个 bean 的创建工作已经全部结束.
整体流程步骤就是这样, 只是忽略了一些和流程无关的细节实现.
总结与感谢:
耗时近三个月, 看了很多 Spring 的源码, 写了 16 篇《品 Spring》系列文章.
从 bean 定义是什么入手, 到现在 bean 实例已经创建好且可用了.
也算是一个阶段性的闭环和里程碑了. 感谢每次都点开公众号的读者.
希望都能学到一些知识, 也欢迎持续关注本号后续文章.
>>> 品 Spring 系列文章 <<< 品 Spring: 帝国的基石
品 Spring:bean 定义上梁山
品 Spring: 实现 bean 定义时采用的 "先进生产力"
品 Spring: 注解终于 "成功上位"
品 Spring: 能工巧匠们对注解的 "加持"
品 Spring:SpringBoot 和 Spring 到底有没有本质的不同?
品 Spring: 负责 bean 定义注册的两个 "排头兵"
品 Spring:SpringBoot 轻松取胜 bean 定义注册的 "第一阶段"
品 Spring:SpringBoot 发起 bean 定义注册的 "二次攻坚战"
品 Spring: 注解之王 @Configuration 和它的一众 "小弟们"
品 Spring:bean 工厂后处理器的调用规则
品 Spring: 详细解说 bean 后处理器
品 Spring: 对 @PostConstruct 和 @PreDestroy 注解的处理方法
品 Spring: 对 @Resource 注解的处理方法
品 Spring: 对 @Autowired 和 @Value 注解的处理方法
>>> 热门文章集锦 <<<
毕业 10 年, 我有话说
[面试] 我是如何面试别人 List 相关知识的, 深度有点长文
我是如何在毕业不久只用 1 年就升为开发组长的
爸爸又给 Spring MVC 生了个弟弟叫 Spring webFlux
[面试] 我是如何在面试别人 Spring 事务时 "套路" 对方的
[面试] Spring 事务面试考点吐血整理 (建议珍藏)
[面试] 我是如何在面试别人 Redis 相关知识时 "软怼" 他的
[面试] 吃透了这些 Redis 知识点, 面试官一定觉得你很 NB(干货 | 建议珍藏)
[面试] 如果你这样回答 "什么是线程安全", 面试官都会对你刮目相看 (建议珍藏)
[面试] 迄今为止把同步 / 异步 / 阻塞 / 非阻塞 / BIO/NIO/AIO 讲的这么清楚的好文章 (快快珍藏)
[面试] 一篇文章帮你彻底搞清楚 "I/O 多路复用" 和 "异步 I/O" 的前世今生 (深度好文, 建议珍藏)
[面试] 如果把线程当作一个人来对待, 所有问题都瞬间明白了
Java 多线程通关 --- 基础知识挑战
品 Spring: 帝国的基石
作者是工作超过 10 年的码农, 现在任架构师. 喜欢研究技术, 崇尚简单快乐. 追求以通俗易懂的语言解说技术, 希望所有的读者都能看懂并记住. 下面是公众号和知识星球的二维码, 欢迎关注!
来源: https://www.cnblogs.com/lixinjie/p/taste-spring-016.html