Spring 源码阅读 23:创建 Bean 实例前的准备工作
基于 Spring Framework v5.2.6.RELEASE
前情提要
之前用 3 篇文章,通过阅读源码,分析了通过 Spring 容器的getBean
方法,获取 Bean 实例的第一种情况,也就是,在单例实例已经被创建的情况下,如何从容器中获取到 Bean 的实例对象或者其对应的 FactoryBean 对象。接下来,将分析另外一种情况,就是,当 Spring 的容器缓存中找不到我们想要的 Bean 实例的时候,Spring 怎样把这个实例创建出来。这一部分比上一种情况更复杂,因此也会通过多篇文章来深入分析。
为保证连贯性,希望你从 Spring Framework 源码解读 专栏中的第 19 篇开始阅读。
在 AbstractBeanFactory 的doGetBean
方法中,如果无法从容器缓存中获取到 Bean 实例对象,那么,代码流程将会走进第一个else
语句块,之后所有的流程,都在这个语句块中。下面开始按照代码流程逐步分析。
检查 Prototype Bean 的创建状态
首先,会有一个if
语句判断beanName
对应的 Bean 是不是一个原型 Bean 且正在被创建,如果是,则抛出异常。
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
这里的isPrototypeCurrentlyInCreation方法跟之前见到过的isSingletonCurrentlyInCreation方法很类似,其实就是判断beanName是不是在一个集合中存在,这个集合中缓存着正在被创建的原型 Bean 的名称。
// org.springframework.beans.factory.support.AbstractBeanFactory#isPrototypeCurrentlyInCreation
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
但是与单例的 Bean 在整个容器中只存在一个实例对象不同,原型 Bean 在每次通过getBean方法获取 Bean 实例的时候,都会创建一个新的实例,因此,不存在从容器缓存中获取的情况,因此,代码执行到这里如果 Bean 的实例正在被创建的话,直接报错即可。
从父容器中获取 Bean 实例对象
接下来,Spring 尝试从父容器中获取 Bean 的实例对象。
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
如果当前的容器存在父容器,并且当前容器不存在beanName
对应的 BeanDefinition,那么,会尝试从父容器中获取 Bean 实例。这里要留意,虽然这一段逻辑相对靠前,但是,根据判断可以知道,只有在当前容器不存在相应的 BeanDefinition 的情况下,才会尝试从高父容器获取实例。
按照之前的源码分析,默认情况下,Spring 在初始化容器的时候,是没有父容器的,因此,不会进入这段逻辑。不过我们凭着兴趣也看看这里面的逻辑。
在if语句块的开头,调用了一个originalBeanName
的方法,并且注意,这里传入的参数是name
而不是beanName
,也就是这里传入了调用getBean
方法时提供的名称,而不是经过转换的规范名称。进入方法,看看这里做了什么。
// org.springframework.beans.factory.support.AbstractBeanFactory#originalBeanName
protected String originalBeanName(String name) {
String beanName = transformedBeanName(name);
if (name.startsWith(FACTORY_BEAN_PREFIX)) {
beanName = FACTORY_BEAN_PREFIX + beanName;
}
return beanName;
}
这里首先调用transformedBeanName
方法,通过name
获取到了规范名称beanName
,这段逻辑我们之前已经了解过了。接着,如果name
是以&
符号开头的,那就给beanName
也加上&前缀。
因为name
的值有可能是 Bean 的别名,因此,如果去父容器中查找的话,需要使用 Bean 的唯一标识符,因此在这里进行了转换,又因为,名称是否包含&
前缀表明了调用方是想获取 Bean 实例对象还是其对应的 FactoryBean 工厂实例,因此,&前缀要保持原样。
处理完名称之后,就尝试从父容器中获取,然后将获取的结果返回,因为是在当前容器没有beanName
对应的 BeanDefinition 的情况下,才尝试从父容器获取,这里算是一个托底的步骤,所以,不管有没有获取到,都直接将结果返回。
标记创建
接着看后面的代码:
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
根据之前的代码分析,在调用doGetBean
方法的时候,typeCheckOnly
参数传入的值是false
,因此这里会进入语句块,执行markBeanAsCreated
方法。
// org.springframework.beans.factory.support.AbstractBeanFactory#markBeanAsCreated
/**
* Mark the specified bean as already created (or about to be created).
* <p>This allows the bean factory to optimize its caching for repeated
* creation of the specified bean.
* @param beanName the name of the bean
*/
protected void markBeanAsCreated(String beanName) {
if (!this.alreadyCreated.contains(beanName)) {
synchronized (this.mergedBeanDefinitions) {
if (!this.alreadyCreated.contains(beanName)) {
// Let the bean definition get re-merged now that we're actually creating
// the bean... just in case some of its metadata changed in the meantime.
clearMergedBeanDefinition(beanName);
this.alreadyCreated.add(beanName);
}
}
}
}
这个方法的作用就是,标记当前beanName
对应的 Bean 实例已经被创建或者即将被创建。是一项性能优化,也算是在真正创建 Bean 之前执行的准备工作。
获取 BeanDefinition
接下来进入了一个try
语句块,接着看里面的代码:
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
这里获取了 BeanDefinition,看来创建 Bean 实例对象的工作就快要开始了。不过,可以注意到,这里获取到的 BeanDefinition 的类型是 RootBeanDefinition,而不是容器初始化时创建的 GenericBeanDefinition。
关于几个重要的 BeanDefinition 实现类,之前的文章中做过简单介绍,可以参考:Spring 源码阅读 11:BeanDefinition 介绍 。
这里来解释一下为什么会获取一个 RootBeanDefinition,在配置文件中定义 Bean 的信息的时候,Bean 之间是可以存在父子关系的。具有父子关系的 BeanDefinition 在实际使用之前,Spring 会将它们的信息进行合并,得到 RootBeanDefinition,这里的方法getMergedLocalBeanDefinition
获取到的就是合并后的 BeanDefinition。
进入方法查看源码:
// org.springframework.beans.factory.support.AbstractBeanFactory#getMergedLocalBeanDefinition
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
// Quick check on the concurrent map first, with minimal locking.
RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
if (mbd != null && !mbd.stale) {
return mbd;
}
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}
首先会从mergedBeanDefinitions
集合中获取beanName
对应的 RootBeanDefinition,如果不存在的话,就调用另外一个重载方法进行 BeanDefinition 的合并,最后返回 RootBeanDefinition 类型的结果。
具有父子关系的 Bean 在实际开发中很少见,合并的流程也并不复杂,而且与我们分析的主线流程关系也不大,因此这里不做深入分析了,你可以自行查看。如果我们配置的 Bean 没有 Parent Bean,那么可以简单理解为使用beanName
对应的 GenericBeanDefinition 中的信息创建了一个 RootBeanDefinition。
在得到 RootBeanDefinition 之后,调用了checkMergedBeanDefinition方法。
// org.springframework.beans.factory.support.AbstractBeanFactory#checkMergedBeanDefinition
protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args)
throws BeanDefinitionStoreException {
if (mbd.isAbstract()) {
throw new BeanIsAbstractException(beanName);
}
}
这里就是一个检查,确保它不是一个无法实例化的抽象的 BeanDefinition。
初始化依赖的 Bean
如果之前的检查没有问题,那么,现在已经有了一个可用的 BeanDefinition。不过,在真正初始化 Bean 实例对象之前,还需要实例化它依赖的 Bean 实例。
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
首先会从 BeanDefinition 中获取到所有的依赖 Bean 的名称,然后进行遍历,对每个依赖 Bean 的名称执行如下操作:
- 通过
isDependent
方法,判断被依赖 Bean,并不依赖当前的要创建的 Bean,也就是它们没有循环的依赖关系。 - 通过
registerDependentBean
方法,在容器中注册它们的依赖关系。 - 通过getBean方法初始化依赖的 Bean。
接下来分别看这几个步骤。先看isDependent
方法。
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isDependent(java.lang.String, java.lang.String)
protected boolean isDependent(String beanName, String dependentBeanName) {
synchronized (this.dependentBeanMap) {
return isDependent(beanName, dependentBeanName, null);
}
}
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isDependent(java.lang.String, java.lang.String, java.util.Set<java.lang.String>)
private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {
if (alreadySeen != null && alreadySeen.contains(beanName)) {
return false;
}
String canonicalName = canonicalName(beanName);
Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
if (dependentBeans == null) {
return false;
}
if (dependentBeans.contains(dependentBeanName)) {
return true;
}
for (String transitiveDependency : dependentBeans) {
if (alreadySeen == null) {
alreadySeen = new HashSet<>();
}
alreadySeen.add(beanName);
if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
return true;
}
}
return false;
}
这里的判断逻辑并不难理解,不仅判断了直接依赖的情况,还判断了简介依赖的情况。判断的依据是容器中的dependentBeanMap
集合,定义如下:
/** Map between dependent bean names: bean name to Set of dependent bean names. */
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
判断无误之后,在容器中注册依赖关系的方法registerDependentBean
代码如下:
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerDependentBean
public void registerDependentBean(String beanName, String dependentBeanName) {
String canonicalName = canonicalName(beanName);
synchronized (this.dependentBeanMap) {
Set<String> dependentBeans =
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
if (!dependentBeans.add(dependentBeanName)) {
return;
}
}
synchronized (this.dependenciesForBeanMap) {
Set<String> dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
dependenciesForBean.add(canonicalName);
}
}
向容器中的dependentBeanMap
和dependenciesForBeanMap
中添加了依赖关系,注意这两个集合的定义:
/** Map between dependent bean names: bean name to Set of dependent bean names. */
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
/** Map between depending bean names: bean name to Set of bean names for the bean's dependencies. */
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
它们两者的区别就是,Key-Value 映射关系是相反的,应该是为了方便根据依赖关系相互查找。
这里为什么存在依赖关系会抛出异常呢?我的理解是这样的:这两个集合当中保存的依赖关系,是在创建 Bean 实例的过程中添加的,如果当前要创建的 Bean 和所以来的 Bean 存在循环依赖的关系,并且dependentBeanMap
集合中存在它们的依赖关系,那么,当前要创建的 Bean 的实例应该是已经开始被创建的状态才对,因此这里会报错。
之后,就是调用getBean
创建以来的 Bean 实例了,这里是一个递归调用,执行的就是我们现在正在分析的流程。
总结
本文先分析到这里,主要是创建 Bean 实例对象之前的一些准备工作,接下来将进入创建 Bean 实例的部分。下篇文章见。
转载自:https://juejin.cn/post/7137094422683975716