SpringApplication
是 springboot 应用的启动类的引导类
run()
是 SpringApplication
中,我们主要使用的方法
PostProcessor
是 springboot 在初始化过程中的重要组件,包含多种类型,如 BeanFactoryPostProcessors
、BeanPostProcessors
分别用来完成 bean 工厂和 bean 的初始化动作
一些与 springboot 整合的框架也会频繁使用类似的方式,比如 KafkaListenerAnnotationBeanPostProcessor
AbstractApplicationContext
在 springboot 中扮演重要角色,大部分启动流程实际是通过这个类完成的
ConfigurableListableBeanFactory
是 springboot 实例工厂的接口
其默认实现为 DefaultListableBeanFactory
,主要依赖它来实例化对象
DefaultListableBeanFactory
继承自 AbstractBeanFactory
关键方法 doGetBean
和 createBean
在这个抽象类的中
doGetBean
在此类中实现,方法定义了从尝试直接获取对象,到创建对象的流程
doGetBean
在此类中定义,在子类 AbstractAutowireCapableBeanFactory
中实现,用于真正创建实例
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
DefaultSingletonBeanRegistry
是 Spring 用于单例模式的 bean 注册器
singletonObjects
、earlySingletonObjects
、singletonFactories
是 DefaultSingletonBeanRegistry
中的 3 个容器,如下代码
singletonFactories
工厂缓存,作为三级缓存,存放 beanName<–>bean factoryearlySingletonObjects
早期预览缓存,作为二级缓存,存放 beanName<–>early bean(没有完全创建完)singletonObjects
完全缓存,作为一级缓存,存放 beanName<–>bean(完全创建完)/** Cache of singleton objects: bean name to bean instance. */
//1级, 已经完全实例化好了的 bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
//2级, 没有完全实例化好的 bean
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
//3级, bean 的工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
进入 SpringApplication.run
,通过 refreshContext
刷新容器
然后进入 AbstractApplicationContext.refresh
这一步骤中注册 spring bean 的后置处理器
依赖注入,基于其中一个 spring bean 的后置处理器 实现
然后进入 AbstractApplicationContext.finishBeanFactoryInitialization
,完成 spring bean 工厂的初始化
此方法的最后一步,就是初始化所有非懒加载的单例对象
这里的 beanFactory
是 DefaultSingletonBeanRegistry
从下图位置 preInstantiateSingletons
开始,springboot 开始实例化非懒加载的单例 beans
为了调试方便,这里使用 条件断点,条件为 bean 的 name,是我们想关注的那几个(否则会有很多 springboot 自己的对象)
1 处,判断当前这个 beanName 对应的是个单例的对象,不是就 pass
2 处,判断当前这个 beanName 对应的是个 工厂,肯定不是,走 else
3 处,正常单例无误,开始获取(获取不到肯定就得创造)
路过若干个重载的 getBean
最终来到 AbstractBeanFactory.doGetBean
在红框位置,检查当前 Bean 是否存在于缓存中,但此时的 bean 还没有标记为创建中,因此被拒绝,如下图红叉处
回到 AbstractBeanFactory.doGetBean
后检查有没有在当前工厂里已经定义了,这里其实是从父工厂里进行检查
看上面的红框,父工厂都是 null ,跳过了
看下面的红框,如果不是只校验类型不校验引用,会将当前 bean 标记为 创建中
接下来检查 bean 是否有 depend on 的限制,当前案例肯定是没有的,跳过
接下来是三种对象的类型的构建,分别是 单例、原型 和 其他
单例,2 处进入创建实例的逻辑,但需要注意,这个逻辑只是定义在红线的匿名内部类中,实际调用时机是 3 处的方法内
原型,可见多了一个 beforePrototypeCreation
方法 和后面的 afterPrototypeCreation
其他,从 1 处可见,在这种情况下,springboot 是懵逼的,所以一言不合就找机会扔异常
同时,从 2 处可见,在这种情况下,4 处提供的 getObject
方法是按照 原型 的过程创建对象的
可以理解为这种创建过程是对 原型 的微调和别名
可以看到默认的其他类型 socpes 如下图所示,可以理解为什么以 原型为模板,因为不可能只有一个 请求 吧
而 其他 scope 和原型的区别在于黄圈的 3 处,这里对于每一种 scope 定义自己 getObject
的行为
这个行为命名为 get
方法,并在 Scope.get
中 调用绿圈 4 处定义的 getObject
的逻辑(即 2 处的逻辑)
当前场景肯定是执行 单例 分支,即上面 单例截图 3 处
来到 DefaultSingletonBeanRegistry.getSingleton
可见 单例截图 的 2 处其实就是下图 1 处的入参,并在下图 2 处被使用
注意上图的 beforeSingletonCreation
更下面还有一个 afterSingletonCreation
可见 beforeXXCreation
、createBean
、afterXXCreation
是 springboot 创建实例的套路
同时此方法将正在创建的 bean 标记为 创建中的单例( 放进 singletonsCurrentlyInDestruction
),这里就是上面 getSingleton
没有通过的地方 ,点此跳转,第二次就不会被此条件拒绝
DefaultSingletonBeanRegistry
类上文说过,是实例化单例对象的重要类,里面有三个 map,作为三级缓存
为了方便观察,添加下面的变量
从此位置,即 DefaultSingletonBeanRegistry.getSingleton
的 2 处 (点击跳转)
进入上文 单例截图,即 AbstractBeanFactory.doGetBean
的 2 处(点击跳转)
进而进入 AbstractAutowireCapableBeanFactory
接下来是一大串这个类中逻辑
从上文AbstractBeanFactory.doGetBean
会进入此类的 createBean
中,可以发现其中有个 doCreateBean
一般,
doXX
方法是XX
方法中的干货
因为一些比较周边的逻辑确实算是 XX 逻辑的一部分,但是这些逻辑没有实际解决问题(比如参数校验啥的) ,在这些逻辑中间通常夹着实际解决问题的逻辑(可以简称为干货逻辑)
而这两种逻辑混在一起并且很长时,阅读者很难区分二者,这时,需要将干货逻辑抽取方法,单独存在
但是,这个干货方法的作用实际上就是XX
,于是常规套路命名为doXX
以作区分(和强调)
首先在 doCreateBean
中,尝试创建对象
获取 bean 的类型,随后通常在下图 2 处尝试是否指定了非空参构造器,如果没有,在 1 处用空参构造器创建对象并返回
然后在 doCreateBean
中,判断当前是否可能存在循环依赖
如果可能(正创建的 bean 是单例,系统参数运行并且已经标记为创建中),则需要将当前 bean 加入工厂缓存,如下图红框
具体逻辑如下
此时可以从以前的表达式看到 AService
已经进入工厂缓存
上图中有个从早期预览缓存删除的逻辑,这是因为在循环依赖场景,标记了 bean 为创建中,但还没有创建出实例(没在完全缓存中),同时系统参数允许,就从这里快速获得,同时将单例放到此缓存中,从注释看,涉及到一个 full singleton lock
接下来 springboot 会尝试给刚刚创建的 AService
的 bean 填充属性(populate 有输入数据的意思)
进入此方法,先尝试获取属性值,现在当然没有
判断是否有创建 bean 过程中需要使用的 beanPostProcessor
从下面的监控里,可以看到有 4 个,其中明显红框处是当前场景真正需要的
@Resource
时,通过 CommonAnnotationBeanPostProcessor
完成注入@Autowired
时,通过 AutowiredAnnotationBeanPostProcessor
完成注入循环至 CommonAnnotationBeanPostProcessor
,进入 postProcessProperties
,如图中步骤
这里发现 bean,即实例化出来的 AService 中有个 BService
的 b
变量
并通过下面的 inject
方法注入
InjectionMetadata.inject
通过其内部类 InjectedElement
完成注入
在这里,获取用于注入的资源,后面如果获取不到就创建,整个过程经过 CommonAnnotationBeanPostProcessor
内部类 ResourceElement.getResourceToInject
、CommonAnnotationBeanPostProcessor.getResource
、CommonAnnotationBeanPostProcessor.autowireResource
、DefaultListableBeanFactory.resolveDependency
最终进入 DefaultListableBeanFactory.doResolveDependency
,如下面系列截图
最终到达 DefaultListableBeanFactory.doResolveDependency
doResolveDependency
会在获取注入类型后尝试获取建议值
如下图,这个建议值是在注解上获取的,可能对应 @Resoutce("xxx")
的场景
在下图取得了需要注入的对象的类型
这里有两种可能性:单实现、多实现
一般情况都是单实现,进入 DependencyDescriptor.resolveCandidate
方法
进去后看到一个有点眼熟的东西,继续向下,又一次进入 AbstractBeanFactory.doGetBean
这里已经是创建被依赖的对象的 bean 了
路过这里
又一次通过 DefaultSingletonBeanRegistry.getSingleton
调用 AbstractBeanFactory.doGetBean
的匿名内部类,进入其子类 AbstractAutowireCapableBeanFactory.createBean
随后又进入其中的 doCreateBean
,并在红框处创建实例
并判断当前是不是可能出现循环依赖,如果是,继续扔到工厂缓存中,如下图 1 处
然后给当前 bean 填充属性,如上图 2 处,注意 此时的当前 bean 已经是 BService 了
有一次循环至 CommonAnnotationBeanPostProcessor
,进入 postProcessProperties
,通过 InjectionMetadata.inject
与其内部类 InjectedElement
注入
尝试注入时,继续获取被注入值的资源
和创建 AService
时一样,路过 CommonAnnotationBeanPostProcessor
内部类 ResourceElement.getResourceToInject
、CommonAnnotationBeanPostProcessor.getResource
、CommonAnnotationBeanPostProcessor.autowireResource
、DefaultListableBeanFactory.resolveDependency
最终进入 DefaultListableBeanFactory.doResolveDependency
,执行下图代码
第三次通过 getBean
,此时又是获取 AService
,如下图
但这时,AService
已经被标记为创建中,于是通过下图 √ 处的校验,并在 1、2、3 处依次查找各级缓存
如果是在工厂缓存中找到的 bean ,就对它做一次缓存升级,从 工厂缓存 提升到 早期预览缓存
如此图效果
而回到 AbstractBeanFactory.doGetBean
时,第二次 getBean( AService )
已经获取到了它的 bean
于是跳过了上次 doGetBean
时走的大段创建过程,到最后一步,从这里返回
回到了 BService
执行了一半的 DefaultListableBeanFactory.doResolveDependency
回到了 CommonAnnotationBeanPostProcessor.autowireResource
并在后面注册依赖
从下面变量看,都注册完成了
继续运行,回到了 InjectionMetadata.InjectedElement.inject
通过红框完成注入(红框底层是 Unsafe 类)
此时,回到 AbstractAutowireCapableBeanFactory.populateBean
应用属性值(但不是注入的意思,之前已经注入成功了)
继续向后,完成 BService
的 createBean
,
即完成了 DefaultSingletonBeanRegistry.getSingleton
中 这一步
并在 DefaultSingletonBeanRegistry.getSingleton
最后添加 BService
进完成缓存
Bservice
进入完成缓存,然后删除 工厂缓存(还删了早期预览缓存,并增加了注册单例,但现在不关心)
退出 AbstractBeanFactory.doGetBean
完成 BService
的创建,并回到 DependencyDescriptor.resolveCandidate
方法
继续,就回到了 DependencyDescriptor.resolveCandidate
并取得了填充 AService
属性 b 的 BService
bean
然后和注册 AService
一样,注册 BService
为依赖,接着完成注入
最后将 AService
加入完成缓存,完成单例
需要注意理解的是,此时,DefaultSingletonBeanRegistry.preInstantiateSingletons
只是在下图的循环中完成了 AService
的创建 这一轮循环,下面会轮到 BService
的创建 的轮次,但 BService
早就创建完成了
springboot 开始实例化非懒加载单例 bean
springboot 实例化 A getBean
的轮次开始
A doGetBean
A 从缓存中获取失败,因为 A 没有标记为创建中
A 实例化
A 标记为创建中,进入 工厂缓存
A 填充属性,通过 postProcessor
获取 A 需要填充的字段,并尝试解决它
尝试解决它 A 需要填充的字段的类型
尝试解决它 A 需要填充的字段的资源(Resource),DependencyDescriptor.resolveCandidate
触发 B 的 getBean
B doGetBean
B 从缓存中获取失败,因为 B 没有标记为创建中
B 实例化
B 标记为创建中,进入 工厂缓存
B 填充属性,通过 postProcessor
获取 B 需要填充的字段,并尝试解决它
尝试解决它 B 需要填充的字段的类型
尝试解决它 B 需要填充的字段的资源(Resource),DependencyDescriptor.resolveCandidate
触发 A 的 getBean
A doGetBean
A 从缓存中获取成功,返回
B 需要填充的字段的资源(Resource) 获取成功
回到 postProcessor
,完成 B 中 a 的注入
B 添加到完成缓存,完成 B 的单例注册
B 创建完成(实例化完成)
==回到 A 触发的 B 的 getBean
==(已完成)
A 需要填充的字段的资源(Resource)已经有了,刚刚 getBean
返回的就是
回到 postProcessor
,完成 A 中 b 的注入
A 添加到完成缓存,完成 B 的单例注册
A 创建完成(实例化完成)
springboot 实例化 A getBean
的轮次结束
springboot 实例化 B getBean
的轮次结束
B doGetBean
B 从缓存中获取成功
springboot 实例化 B getBean
的轮次结束