前言
原本以为,Spring 通过解析 bean 的配置,生成并注册 bean defintions 的过程不太复杂,比较简单,不用单独开辟一篇博文来讲述;但是当在分析前面两个章节有关 @Autowired、@Component、@Service 注解的注入机制的时候,发现,如果没有对有关 bean defintions 的解析和注册机制彻底弄明白,则很难弄清楚 annotation 在 Spring 容器中的底层运行机制;所以,本篇博文作者将试图去弄清楚 Spring 容器内部是如何去解析 bean 配置并生成和注册 bean definitions 的相关主流程;
备注,本文是作者的原创作品,转载请注明出处。
bean definition 是什么?
➥ bean definitions 是什么?
其实很简单,就是 Java 中的 POJO,用来描述 bean 配置中的 element 元素的,比如,我们有如下的一个简单的配置
beans.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.shangyang" />
<bean name="jane" class="org.shangyang.spring.container.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
可以看到,上面有三个 element
- _<beans/>_, root element
- _<context:component/>_, component-scan element
- _<bean/>_, bean element
在配置文件 beans.xml 被 Spring 解析的过程中,每一个 element 将会被解析为一个 bean definition 对象缓存在 Spring 容器中;
➥ 需要被描述为 bean definitions 的配置对象主要分为如下几大类,
- xml-based,解析 xml beans 的情况;
- 使用 @Autowired、@Required 注解注入 beans 的解析情况;
需要特殊处理并解析的元素 <context:annotation-config/> - 使用 @Component、@Service、@Repository,@Beans 注解注入 beans 的解析情况;
需要特殊处理并解析的元素 <context:annotation-scan/>
➥ 最开始我的确是这么认识 bean definitions 的,但是当我分析完有关 bean definitions 的相关逻辑和源码以后,对其认识有了升华,参考写在最后;
源码分析
最好的分析源码的方式,就是通过高屋建瓴,逐个击破的方式;首先通过流程图获得它的蓝图(顶层设计图),然后再根据蓝图上的点逐个击破;最后才能达到融会贯通,胸有成竹的境界;所以,这里作者用这样的方式带你深入剖析 Spring 容器里面的核心点,以及相关主流程到底是如何运作的。
测试用例
为了一次性把上述源码分析所描述有的情况阐述清楚,我们继续使用 Spring Core Container 源码分析六:@Service 中使用的测试用例;唯一做的修改是,再使用一个特殊的 element xmlns:p 来配置 john,这样可以进一步去调试自定义 Spring 配置标签是如何实现的;
beans.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.shangyang" />
<bean name="john"
class="org.shangyang.spring.container.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="org.shangyang.spring.container.Person">
<property name="name" value="Jane Doe"/>
</bean>
<bean name="niba" class="org.shangyang.spring.container.Dog">
<property name="name" value="Niba" />
</bean>
</beans>
流程分析
整个流程是从解析 bean definitions 流程开始的,对应的入口是主流程的 _step 1.1.1.2 obtainFreshBeanFactory_;
入口流程
- 首选初始化得到 BeanFactory 实例 DefaultListableBeanFactory,用来注册解析配置后生成的 bean definitions;
-
然后通过 XmlBeanDefinitionReader 解析 Spring XML 配置文件
根据用户指定的 XML 文件路径 location,进行解析并且得到 Resource[] 对象,具体参考 step 1.1.3.3.1.1 getResource(location) 步骤;这里,对其如何通过 location 得到 Resource[] 对象做进一步分析,看源码,PathMatchingResourcePatternResolver.java
public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { // a class path resource (multiple resources for same name possible) if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { // a class path resource pattern return findPathMatchingResources(locationPattern); } else { // all class path resources with the given name return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); } } else { // Only look for a pattern after a prefix here // (to not get fooled by a pattern symbol in a strange prefix). int prefixEnd = locationPattern.indexOf(":") + 1; if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { // a file pattern return findPathMatchingResources(locationPattern); } else { // a single resource with the given name return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } }
这里的解析过程主要分为两种情况进行解析,一种是前缀是 classpath: 的情况,一种是普通的情况,正如我们当前所使用的测试用例的情况,既是 new ClassPathXmlApplicationContext("beans.xml") 的情况,这里不打算在这里继续深挖;
- 以测试用例 beans.xml 为例,从 #2 解析得到的 Resource 实例 resource 对应的是 beans.xml 的配置信息,从 step 1.1.3.3.1.2 loadBeanDefinitions 开始将会对 resource 既是 beans.xml 中的元素依次进行解析;首先生成对应 beans.xml 的 org.w3c.Document 对象实例 document_,见 _step 1.1.3.3.1.2.2.1_,其次得到解析 _document 对象的 BeanDefinitionDocumentReader 实例 documentReader_,将当前的 Resource 对象封装为 XmlReaderContext 实例 _xmlReaderContext_,最后通过 _documentReader 开始正式解析 document 对象得到 bean definitions 并将其注册到当前的 beanFactory 实例中,该步骤见 step 1.1.3.3.1.2.2.2.3
当完成上述三个步骤以后,将进入 register bean definitions process 流程
register bean definitions process
➥ 首先,重要的两件事情是,
-
从 document 对象中获得了 Root 实例 root_,见 _step 1.2
看一个 root 元素,长什么样的<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>
就是一个 xml 配置文件中的最顶层元素 <beans/>
- 然后初始化得到 documentReader 实例的解析对象既 this.delegate<BeanDefinitionParserDelegate>_,后面针对 _element 元素的解析将会使用到它;
➥ 后续,当前面的工作准备好了以后,来看看是如何解析 element 的?
首先,判断 root 元素的 namespace 对应的是不是 default namespace,若不是,将进入 _step 1.3.3.3: parse custom element_;这里我们关注常规流程,既是当 root 元素的 namespace 是 default namespace 的流程;
遍历 root 元素下的所有 element,
- 若 element 的 namespace 是 default namespace,将进入 parse default element 流程;
比如当前 element 是普通的 <bean/> - 若 element 的 namespace 不是 default namespace,将进入 parse custom element 流程;
比如当前 element 是 <context:annotation-config/> 或者是 <context:component-scan/>
parse default element process
可以看到,该流程中包含四个子流程,依次处理不同的 element 元素的情况,其它三种都是比较特殊的情况,我们这里,主要关注“解析 <bean/>" 元素的流程”
解析 bean element 流程
这里,为了能够尽量的展示出解析 <bean/> 元素的流程中的逻辑,我将使用一个比较特殊的 <bean/> 来梳理此部分的流程;
<bean name="john"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
该 <bean/> 元素使用了 namespace xmlns:p="http://www.springframework.org/schema/p"
➥ 首先,通过 BeanDefintionParserDelegate 对象解析该 element,得到一个 BeanDefinitionHolder 对象 bdHolder 实例;该解析过程中会依次去解析 bean id, bean name, 以及相关的 scope, init, autowired model 等等属性;见 step 1.1
➥ 其次,对 bean definition 进行相关的修饰操作,见 step 1.2
常规步骤
- 遍历当前 element 中的所有 attributes,依次得到 atttribute node
- 取得 node 所对应的 namespace URI,并判断该 namespace 是否是 custom namespace,如果是 custom namespace,那么正式进入对该 attribute node 的修饰过程,如下所述;
attribute node 的修饰过程
假设,我们当前的 attribute node 为 _p:spouse-ref="jane"_,看看该属性是如何被解析的,
- 首先,通过 node namespace 得到对应的 NamespaceHandler 实例 handler
通过 xmlns:p="http://www.springframework.org/schema/p" 得到的 NamespaceHandler 为 SimplePropertyNamespaceHandler 对象; - 其次,调用 SimplePropertyNamespaceHandler 对象对当前的元素进行解析;
可以看到,前面的解析并没有什么特殊的,从元素 p:spouse-ref="jane" 中解析得到 propery name: _spouse-ref_,property value: _jane_;但是后续解析,比较特殊,需要处理 REF_SUFFIX 的情况了,也就是当 property name 的后缀为 -ref 的情况,表示该 attribute 是一个 ref-bean 属性,其属性值引用的是其它的 bean 实例,所以呢,这里将其 property value 封装为了一个RuntimeBeanReference
对象实例,表示将来在解析该 property value 为 Java Object 的时候,需要去初始化其引用的 bean 实例 _jane_,然后注入到当前的 property value 中; - 最后,将解析后得到的 bean definition 封装在 bean definition holder 对象中进行返回;
➥ 最后,注册 bean definition;
见 step 1.3.2 register.registerBeanDefinition(beanName, beanDefinition)_,_register 就是当前的 bean factory 实例,通过将 bean name 和 bean definition 以键值对的方式在当前的 bean factory 中进行注册;这样,我们就可以通过 bean 的名字,得到其对应的 bean definition 对象了;
➥ 写在该小节最后,
我们也可以自定义某个 element 或者 element attribute,并且定义与之相关的 namespace 和 namespace handler,这样,就可以使得 Spring 容器解析自定义的元素;类似于 dubbo 配置中所使用的 <dubbo /> 自定义元素那样;
parse custom element process
此步骤对应 register bean definitions process 步骤中的 step 1.3.3.2
该小节我将试图使用一个常用的 custom element: <context:component-scan/> 来梳理整个流程;
- 首先得到与 <context:component-scan /> 元素相关的 namespace uri: http://www.springframework.or...,见 _step 1.1
- 通过 #1 得到的 namespace uri 解析得到相应的 NamespaceHandler,这里得到的是 ContextNamespaceHandler_;见 _step 1.2
从 step 1.2.1 getHandlerMappings() 返回了所有内置的 namespace uri 与 namespace handler 所一一对应的键值对; - 使用 #2 返回的 NamespaceHandler 既 ContextNamespaceHandler 进行 parse 操作,见 _step 1.3_,参考子流程 parse element by ContextNamespaceHandler,注意,之所以这里单独使用一个子流程来介绍,是因为使用 ContextNamespaceHandler 来解析只是其中的一种解析情况,将来考虑分析更多的子流程情况;
parse element by ContextNamespaceHandler
继续 parse custom element process 章节中所使用到的例子,<context:component-scan/> 来分析该流程,
➥ 在开始分析之前,看看 component-scan 元素长什么样,
注意,_component-scan_ element 本身包含 annotation-config attribute;
➥ 流程分析
首先,根据 element name: component-scan 找到对应的 BeanDefinitionParser,在 ContextNamespaceHandler 初始化的时候,便初始化设置好 8 对内置的 element name 与 parsers 的键值对;这里,根据名字 component-scan 找到对应的 parser ComponentScanBeanDefinitionParser 对象;
其次,使用 ComponentScanBeanDefinitionParser 对象开始解析工作,
- 首先,解析 <context:component-scan base-package="org.shangyang"/> 得到 basePcakges String[] 对象;
- 其次,初始化得到 ClassPathBeanDefinitiionScanner 对象实例 scanner_,然后调用 _scanner.doScan 方法进入 do scan 流程,该流程中将会遍历 base package 中所包含的所有 .class 文件,解析之,并生成相应的 bean definitions;另外在这个流程中,还要注意的是,最后会将 bean definitions 在当前的 bean factory 对象中进行注册;
- 最后,这一步是从 step 1.2.4 开始,主要处理的逻辑为,当 element 含有 annotation-config 属性的时候,将会注册一系列的 post-processors-bean-definitions;
do scan 流程
这里主要介绍上一个小节中 #2 步骤中所提到的 do scan 流程步骤,对应 parse element by ContextNamespaceHandler 流程图中的 _step 1.2.3 scanner.doScan_;
➥ 先来看看 step 1.2.3.1 findCandidateComponent(basePackage)
ClassPathScanningCandidateComponentProvider.java (已删除大量不相干代码)
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
//1. 从当前用户自定的 classpath 子路径中,通过 regex 查询到所有的所匹配的 resources;要特别注意的是,
// 这里为什么不直接通过 Class Loader 去获取 classes 来进行判断? 因为这样的话就相当于是加载了 Class Type,而 Class Type 的加载过程是通过 Spring 容器严格控制的,是不允许随随便便加载的
// 所以,取而代之,使用一个 File Resource 去读取相关的字节码,从字节码中去解析........
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
//2. 依次遍历用户定义的 bean Class 对象
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
// 将从字节码中获取到的相关 annotation(@Service) 以及 FileSystemResource 对象保存在 metadataReader 当中;
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
...
}
...
}
...
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
-
代码第 10 行
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
这一步通过递归搜索 base package 目录下的所有 .class 文件,并将其字节码封装成 Resource[] 对象;上面的注释解释得非常清楚了,这里封装的是 .class 文件的字节码,而非 class type;除了注解中所描述的,这里再引申说明下,这里为什么不直接加载其 Class Type 还有一个原因就是当 Spring 在加载 Class Type 的时候,很有可能在该 Class Type 上配置了 AOP,通过 ASM 字节码技术去修改原有的字节码以后,再加入 Class Loader 中;所以,之类不能直接去解析 Class Type,而只能通过字节码的方式去解析;
这一步同样
告诫
我们,在使用 Spring 容器来开发应用的时候,开发者不要随随便便的自行加载 Class Type 到容器中
,因为有可能在加载 Class Type 之前需要通过 Spring 容器的 ASM AOP 进行字节码的修改以后再加载; -
代码第 23 行
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
解析当前的 .class 字节码,解析出对应的 annotation,比如 @Service,并将其协同 FileSystemResource 对象一同保存到 metadataReader 对象中;
-
代码第 24 行
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { // includedFilters 包含三类 annotation,1. @Component 2. @ManagedBean 3. @Named if (tf.match(metadataReader, this.metadataReaderFactory)) { return isConditionMatch(metadataReader); } } return false; }
既是从当前的 metadataReader 中去判断是否存在 1. @Component 2. @ManagedBean 3. @Named 三种注解中的一种,如果是,则进入下面的流程
-
代码 25 - 29 行,将符合 #3 标准的 annotation 封装为 ScannedGenericBeanDefinition annotation-bean-definition,并加入 candidates 返回
if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); if (isCandidateComponent(sbd)) { candidates.add(sbd); } ... }
➥ 依次处理并注册返回的 candidates
该步骤从流程图 parse element by ContextNamespaceHandler 中的 step 1.2.3.2 开始,主要做了如下几件事情,
- 设置 candiate (既 annotation bean definition) 的 scope
-
通过 AnnotationBeanNameGenerator 生成 bean name,因为通过 @Component、@Service 注解的方式注入的 bean 往往没有配置 bean name,所以往往需要通过程序的方式自行生成相应的 bean name,看看内部的源码,如何生成 bean name 的,
/**
*/
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); // 处理诸如 @Service("dogService") 的情况
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// Fallback: generate a unique default bean name. 里面的实现逻辑就是通过将 Class Name 的首字母大写编程小写,然后返回;
return buildDefaultBeanName(definition, registry);
}
通常情况下,是将类名的首字母进行小写并返回;对应 _step 1.2.2.3.3_
3. 设置 annotation bean definition 的默认值,参考 _step 1.2.4_
4. 设置 scoped proxy 到当前的 annotation bean definition
5. 最后,将 annotation bean definition 注册到当前的 bean factory
###### 注册 post-processor-bean-definitions
该步骤从流程图 [parse element by ContextNamespaceHandler](#parse-element-by-ContextNamespaceHandler) 的 _step 1.2.4.2 registerAnnotationConfigProcessors_ 开始,将会依次注册由如下 post-processor class 对象所对应的 post-processor-bean-definitions,
+ ConfigurationClassPostProcessor.class
+ AutowiredAnnotationBeanPostProcessor.class
+ RequiredAnnotationBeanPostProcessor.class
+ CommonAnnotationBeanPostProcessor.class
+ 通过 PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME 发射得到的 class
+ EventListenerMethodProcessor.class
+ DefaultEventListenerFactory.class
注意,这里都是通过 Class 对象注册的,并非注册的实例化对象,下面,我们来简单分析一下注册相关的源码,以注册 _AutowiredAnnotationBeanPostProcessor_ post-processor-bean-definition 为例子,
_AnnotationConfigUtils#registerAnnotationConfigProcessors_
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
// 将 AutowiredAnnotationBeanPostProcessor.class 封装为 bean definition
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
上面的步骤将 AutowiredAnnotationBeanPostProcessor.class 封装为 bean definition;
_AnnotationConfigUtils.registerPostProcessor_
private static BeanDefinitionHolder registerPostProcessor(
BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(beanName, definition); // 注册 bean definition
return new BeanDefinitionHolder(definition, beanName);
}
这一步将 _AutowiredAnnotationBeanPostProcessor_ 所对应的 bean definition 注入了当前的 bean factory 当中;
_AutowiredAnnotationBeanPostProcessor_ 提供了 @Autowired 注解注入机制的实现,详情参考 [AutowiredAnnotationBeanPostProcessor](/2017/04/05/spring-core-container-sourcecode-analysis-annotation-autowired/#AutowiredAnnotationBeanPostProcessor) 章节;
## 写在最后
通过上述的分析,可以清晰的看到,bean definition 的作用是什么,就是通过 bean definition 中的描述去限定通过 Class Type 实例化得到 instance 的业务规则,我们看看由 [do scan 流程](#do-scan-流程) 所生成的 annotation-bean-definition<ScannedGenericBeanDefinition> 对象,
{% asset_img debug-scanned-generic-bean-definition.png %}
可以看到,当我们在后续要根据该 annotation-bean-definition 得到一个 DogService 实例的时候,所要遵循的业务规则,如下所示,
Generic bean: class [org.shangyang.spring.container.DogService];
scope=;
abstract=false;
lazyInit=false;
autowireMode=0;
dependencyCheck=0;
autowireCandidate=true;
primary=false;
factoryBeanName=null;
factoryMethodName=null;
initMethodName=null;
destroyMethodName=null;
defined in file [/Users/mac/workspace/spring/framework/sourcecode-analysis/spring-core-container/spring-sourcecode-test/target/classes/org/shangyang/spring/container/DogService.class]
不过,要注意,这里所得到的 ScannedGenericBeanDefinition 实例,同样没有真正去加载 _org.shangyang.spring.container.DogService_ Class Type 到容器中,而只是将 class name `字符串`赋值给了 ScannedGenericBeanDefinition.beanClass,言外之意,将来在加载 Class Type 到容器中的时候,或许与实例化 instance 一样也要根据 bean definitions 中的规则来限定其加载行为,目前我所能够想到的与其相关的就是 ASM 字节码技术,可以在 bean definition 中定义 ASM 字节码修改规则,来控制相关 Class Type 的加载行为;
# References
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。