Spring IoC container role shown above, it will be loaded in some way Configuration Metadata, parse it registered into the container, and then binds the object based on the information of the whole system, assembled into a final usable based light application of the order containers.
In the Spring achieve the above functions, the entire process is divided into two phases: the initialization phase and the container loading phase bean. They are as follows:
1, container initialization phase:
- First, load the Configuration Metadata in some way (mainly based Resource, ResourceLoader two systems).
- Then, the container will be loaded Configuration MetaData parsed and analyzed, and the analysis of the information is assembled into BeanDefinition.
- Finally, BeanDefinition to save the registration in BeanDefinitionRegistry.
So far, Spring IoC initialization is complete.
2, loaded Bean stages:
- After the container initialization phase, bean information defined in the application has been fully loaded into the system, and when we show or implicitly calling BeanFactory.getBean (...) method, Bean load phase is triggered.
- Whether at this stage, the container first checks all of the requested object has been initialized completed, and if not, will be based on the object registration information Bean instantiation request and rely on their registration, and then returns it to the requester.
At this point the second phase is completed.
The first phase has been spent in front of more than 10 articles blog-depth analysis (summary reference "[Spring] Sike - IoC of loading BeanDefinitions summary" ). So from the beginning of this second phase of the analysis: Load Bean stage.
1. getBean
When we call display or implicitly BeanFactory#getBean(String name)
when the method is triggered loaded Bean stage. code show as below:
|
-
Internal call
doGetBean(String name, final Class<T> requiredType, Object[] args, boolean typeCheckOnly)
method, which method accepts four parameters:name
: To obtain the name of the BeanrequiredType
: To get the type of beanargs
: Parameters passed when creating Bean. This parameter is limited to use when creating the Bean.typeCheckOnly
: Whether the type checking.
2. doGetBean
#doGetBean(String name, final Class<T> requiredType, Object[] args, boolean typeCheckOnly)
Method, the code is longer, patiently look. code show as below:
|
A long code, the processing logic is quite complex, it will be split forth.
<1>
Office, detailed analysis, see "2.1 beanName get」 .<2>
Office, detailed analysis, see "Get Bean 2.2 from singleton Bean cache」 .<3>
Office, detailed analysis, see "2.3 prototype model relies inspection」 .<4>
Office, detailed analysis, see "Get Bean 2.4 from parentBeanFactory」 .<5>
Office, detailed analysis, see "Bean 2.5 specified mark has been created or will create」 .<6>
Office, detailed analysis, see "2.6 BeanDefinition get」 .<7>
Office, detailed analysis, see "2.7 rely Bean processing」 .<8>
Office, detailed analysis, see "2.8 different scopes Bean instantiation」 .<9>
Office, detailed analysis, see "2.9 type conversion」 .
2.1 acquire beanName
Corresponding to the following code segment:
|
-
Here is the transfer of
name
methods, not necessarily beanName, may be aliasName, there may be FactoryBean, so there need to call the#transformedBeanName(String name)
method,name
for some conversion. code show as below:// AbstractBeanFactory.java protected String transformedBeanName(String name) { return canonicalName(BeanFactoryUtils.transformedBeanName(name)); }
-
调用
BeanFactoryUtils#transformedBeanName(String name)
方法,去除 FactoryBean 的修饰符。代码如下:// BeanFactoryUtils.java /** * Cache from name with factory bean prefix to stripped name without dereference. * * 缓存 {@link #transformedBeanName(String)} 已经转换好的结果。 * * @since 5.1 * @see BeanFactory#FACTORY_BEAN_PREFIX */ private static final Map<String, String> transformedBeanNameCache = new ConcurrentHashMap<>(); /** * 去除 FactoryBean 的修饰符 $ * * 如果 name 以 “&” 为前缀,那么会去掉该 "&" 。 * 例如,name = "&studentService" ,则会是 name = "studentService"。 * * Return the actual bean name, stripping out the factory dereference * prefix (if any, also stripping repeated factory prefixes if found). * @param name the name of the bean * @return the transformed name * @see BeanFactory#FACTORY_BEAN_PREFIX */ public static String transformedBeanName(String name) { Assert.notNull(name, "'name' must not be null"); if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) { // BeanFactory.FACTORY_BEAN_PREFIX = $ return name; } // computeIfAbsent 方法,分成两种情况: // 1. 未存在,则进行计算执行,并将结果添加到缓存、 // 2. 已存在,则直接返回,无需计算。 return transformedBeanNameCache.computeIfAbsent(name, beanName -> { do { beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length()); } while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)); return beanName; }); }
-
实际上,逻辑比较简单,就是去除传入
name
参数的"$"
的前缀。小知识补充。假设配置了一个 FactoryBean 的名字为
"abc"
,那么获取 FactoryBean 创建的 Bean 时,使用"abc"
,如果获取 FactoryBean 本身,使用"$abc"
。另外,&
定义在BeanFactory.FACTORY_BEAN_PREFIX = "&"
上。 -
transformedBeanNameCache
集合的存在,是为了缓存转换后的结果。下次再获取相同的name
时,直接返回缓存中的结果即可。
-
-
调用
#canonicalName(String name)
方法,取指定的alias
所表示的最终 beanName 。// SimpleAliasRegistry.java /** Map from alias to canonical name. */ // key: alias // value: beanName private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16); public String canonicalName(String name) { String canonicalName = name; // Handle aliasing... String resolvedName; // 循环,从 aliasMap 中,获取到最终的 beanName do { resolvedName = this.aliasMap.get(canonicalName); if (resolvedName != null) { canonicalName = resolvedName; } } while (resolvedName != null); return canonicalName; }
- 主要是一个循环获取 beanName 的过程,例如,别名 A 指向名称为 B 的 bean 则返回 B,若 别名 A 指向别名 B,别名 B 指向名称为 C 的 bean,则返回 C。
-
2.2 从单例 Bean 缓存中获取 Bean
对应代码段如下:
|
- 我们知道单例模式的 Bean 在整个过程中只会被创建一次。第一次创建后会将该 Bean 加载到缓存中。后面,在获取 Bean 就会直接从单例缓存中获取。
-
<x>
处,如果从缓存中得到了 Bean 对象,则需要调用#getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd)
方法,对 Bean 进行实例化处理。因为,缓存中记录的是最原始的 Bean 状态,我们得到的不一定是我们最终想要的 Bean 。另外,FactoryBean 的用途如下:From 《Spring 源码深度解析》P83 页
一般情况下,Spring 通过反射机制利用 bean 的 class 属性指定实现类来实例化 bean 。某些情况下,实例化 bean 过程比较复杂,如果按照传统的方式,则需要在 中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring 为此提供了一个 FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 bean 的逻辑。
FactoryBean 接口对于 Spring 框架来说战友重要的地址,Spring 自身就提供了 70 多个 FactoryBean 的实现。它们隐藏了实例化一些复杂 bean 的细节,给上层应用带来了便利。
详细解析,见 《【死磕 Spring】—— IoC 之加载 Bean:从单例缓存中获取单》 。
2.3 原型模式依赖检查
对应代码段如下:
|
Spring 只处理单例模式下得循环依赖,对于原型模式的循环依赖直接抛出异常。主要原因还是在于,和 Spring 解决循环依赖的策略有关。
- 对于单例( Singleton )模式, Spring 在创建 Bean 的时候并不是等 Bean 完全创建完成后才会将 Bean 添加至缓存中,而是不等 Bean 创建完成就会将创建 Bean 的 ObjectFactory 提早加入到缓存中,这样一旦下一个 Bean 创建的时候需要依赖 bean 时则直接使用 ObjectFactroy 。
- 但是原型( Prototype )模式,我们知道是没法使用缓存的,所以 Spring 对原型模式的循环依赖处理策略则是不处理。
详细解析,见 《【死磕 Spring】—— IoC 之加载 Bean:parentBeanFactory 与依赖处理》 。
2.4 从 parentBeanFactory 获取 Bean
对应代码段如下:
|
- 如果当前容器缓存中没有相对应的 BeanDefinition 对象,则会尝试从父类工厂(
parentBeanFactory
)中加载,然后再去递归调用#getBean(...)
方法。
详细解析,见 《【死磕 Spring】—— IoC 之加载 Bean:parentBeanFactory 与依赖处理》 。
2.5 指定的 Bean 标记为已经创建或即将创建
对应代码段如下:
|
详细解析,见 《【死磕 Spring】—— IoC 之加载 Bean:parentBeanFactory 与依赖处理》 。
2.6 获取 BeanDefinition
对应代码段如下:
|
因为从 XML 配置文件中读取到的 Bean 信息是存储在GenericBeanDefinition 中的。但是,所有的 Bean 后续处理都是针对于 RootBeanDefinition 的,所以这里需要进行一个转换。
转换的同时,如果父类 bean 不为空的话,则会一并合并父类的属性。
详细解析,见 《【死磕 Spring】—— IoC 之加载 Bean:parentBeanFactory 与依赖处理》 。
2.7 依赖 Bean 处理
对应代码段如下:
|
- 每个 Bean 都不是单独工作的,它会依赖其他 Bean,其他 Bean 也会依赖它。
- 对于依赖的 Bean ,它会优先加载,所以,在 Spring 的加载顺序中,在初始化某一个 Bean 的时候,首先会初始化这个 Bean 的依赖。
详细解析,见 《【死磕 Spring】—— IoC 之加载 Bean:parentBeanFactory 与依赖处理》 。
2.8 不同作用域的 Bean 实例化
对应代码段如下:
|
- Spring Bean 的作用域默认为 singleton 。当然,还有其他作用域,如 prototype、request、session 等。
- 不同的作用域会有不同的初始化策略。
详细解析,见 《【死磕 Spring】—— IoC 之加载 Bean:分析各 scope 的 Bean 创建》 。
2.9 类型转换
对应代码段如下:
|
- 在调用
#doGetBean(...)
方法时,有一个 requiredTyp
e 参数。该参数的功能就是将返回的 Bean 转换为requiredType
类型。 - 当然就一般而言,我们是不需要进行类型转换的,也就是
requiredType
为空(比如#getBean(String name)
方法)。但有,可能会存在这种情况,比如我们返回的 Bean 类型为 String ,我们在使用的时候需要将其转换为 Integer,那么这个时候requiredType
就有用武之地了。当然我们一般是不需要这样做的。
详细解析,见 TODO
3. 小结
至此 BeanFactory#getBean(...)
方法的过程讲解完了。后续将会对该过程进行拆分,更加详细的说明,弄清楚其中的来龙去脉,所以这篇博客只能算是 Spring Bean 加载过程的一个概览。拆分主要是分为三个部分:
- 分析从缓存中获取单例 Bean ,以及对 Bean 的实例中获取对象。
- 如果从单例缓存中获取 Bean ,Spring 是怎么加载的呢?所以第二部分是分析 Bean 加载,以及 Bean 的依赖处理。
- Bean 已经加载了,依赖也处理完毕了,第三部分则分析各个作用域的 Bean 初始化过程。
老艿艿:再推荐几篇不错的 Spring Bean 加载的过程,写的不错的文章:
- zhanglbjames 《Spring-IoC-getBean源码解析》
- glmapper 《Spring源码系列:依赖注入(一)getBean》
- 是Guava不是瓜娃 《Spring原理与源码分析系列(五)- Spring IoC源码分析(下)》