您好,登錄后才能下訂單哦!
原本以為,Spring 通過(guò)解析 bean 的配置,生成并注冊(cè) bean defintions 的過(guò)程不太復(fù)雜,比較簡(jiǎn)單,不用單獨(dú)開(kāi)辟一篇博文來(lái)講述;但是當(dāng)在分析前面兩個(gè)章節(jié)有關(guān) @Autowired、@Component、@Service 注解的注入機(jī)制的時(shí)候,發(fā)現(xiàn),如果沒(méi)有對(duì)有關(guān) bean defintions 的解析和注冊(cè)機(jī)制徹底弄明白,則很難弄清楚 annotation 在 Spring 容器中的底層運(yùn)行機(jī)制;所以,本篇博文作者將試圖去弄清楚 Spring 容器內(nèi)部是如何去解析 bean 配置并生成和注冊(cè) bean definitions 的相關(guān)主流程;
備注,本文是作者的原創(chuàng)作品,轉(zhuǎn)載請(qǐng)注明出處。
? bean definitions 是什么?
其實(shí)很簡(jiǎn)單,就是 Java 中的 POJO,用來(lái)描述 bean 配置中的 element 元素的,比如,我們有如下的一個(gè)簡(jiǎn)單的配置
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>
可以看到,上面有三個(gè) element
在配置文件 beans.xml 被 Spring 解析的過(guò)程中,每一個(gè) element 將會(huì)被解析為一個(gè) bean definition 對(duì)象緩存在 Spring 容器中;
? 需要被描述為 bean definitions 的配置對(duì)象主要分為如下幾大類,
? 最開(kāi)始我的確是這么認(rèn)識(shí) bean definitions 的,但是當(dāng)我分析完有關(guān) bean definitions 的相關(guān)邏輯和源碼以后,對(duì)其認(rèn)識(shí)有了升華,參考寫(xiě)在最后;
最好的分析源碼的方式,就是通過(guò)高屋建瓴,逐個(gè)擊破的方式;首先通過(guò)流程圖獲得它的藍(lán)圖(頂層設(shè)計(jì)圖),然后再根據(jù)藍(lán)圖上的點(diǎn)逐個(gè)擊破;最后才能達(dá)到融會(huì)貫通,胸有成竹的境界;所以,這里作者用這樣的方式帶你深入剖析 Spring 容器里面的核心點(diǎn),以及相關(guān)主流程到底是如何運(yùn)作的。
為了一次性把上述源碼分析所描述有的情況闡述清楚,我們繼續(xù)使用 Spring Core Container 源碼分析六:@Service 中使用的測(cè)試用例;唯一做的修改是,再使用一個(gè)特殊的 element xmlns:p 來(lái)配置 john,這樣可以進(jìn)一步去調(diào)試自定義 Spring 配置標(biāo)簽是如何實(shí)現(xiàn)的;
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>
整個(gè)流程是從解析 bean definitions 流程開(kāi)始的,對(duì)應(yīng)的入口是主流程的 step 1.1.1.2 obtainFreshBeanFactory;
首選初始化得到 BeanFactory 實(shí)例 DefaultListableBeanFactory,用來(lái)注冊(cè)解析配置后生成的 bean definitions;
然后通過(guò) XmlBeanDefinitionReader 解析 Spring XML 配置文件
根據(jù)用戶指定的 XML 文件路徑 location,進(jìn)行解析并且得到 Resource[] 對(duì)象,具體參考 step 1.1.3.3.1.1 getResource(location) 步驟;這里,對(duì)其如何通過(guò) location 得到 Resource[] 對(duì)象做進(jìn)一步分析,看源碼,
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)};
}
}
}
這里的解析過(guò)程主要分為兩種情況進(jìn)行解析,一種是前綴是 classpath: 的情況,一種是普通的情況,正如我們當(dāng)前所使用的測(cè)試用例的情況,既是 new ClassPathXmlApplicationContext("beans.xml") 的情況,這里不打算在這里繼續(xù)深挖;
當(dāng)完成上述三個(gè)步驟以后,將進(jìn)入 register bean definitions process 流程
? 首先,重要的兩件事情是,
<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>
就是一個(gè) xml 配置文件中的最頂層元素 <beans/>
? 后續(xù),當(dāng)前面的工作準(zhǔn)備好了以后,來(lái)看看是如何解析 element 的?
首先,判斷 root 元素的 namespace 對(duì)應(yīng)的是不是 default namespace,若不是,將進(jìn)入 step 1.3.3.3: parse custom element;這里我們關(guān)注常規(guī)流程,既是當(dāng) root 元素的 namespace 是 default namespace 的流程;
遍歷 root 元素下的所有 element,
可以看到,該流程中包含四個(gè)子流程,依次處理不同的 element 元素的情況,其它三種都是比較特殊的情況,我們這里,主要關(guān)注“解析 <bean/>" 元素的流程”
這里,為了能夠盡量的展示出解析 <bean/> 元素的流程中的邏輯,我將使用一個(gè)比較特殊的 <bean/> 來(lái)梳理此部分的流程;
<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"
? 首先,通過(guò) BeanDefintionParserDelegate 對(duì)象解析該 element,得到一個(gè) BeanDefinitionHolder 對(duì)象 bdHolder 實(shí)例;該解析過(guò)程中會(huì)依次去解析 bean id, bean name, 以及相關(guān)的 scope, init, autowired model 等等屬性;見(jiàn) step 1.1
? 其次,對(duì) bean definition 進(jìn)行相關(guān)的修飾操作,見(jiàn) step 1.2
常規(guī)步驟
attribute node 的修飾過(guò)程
假設(shè),我們當(dāng)前的 attribute node 為 p:spouse-ref="jane",看看該屬性是如何被解析的,
首先,通過(guò) node namespace 得到對(duì)應(yīng)的 NamespaceHandler 實(shí)例 handler
通過(guò) xmlns:p="http://www.springframework.org/schema/p" 得到的 NamespaceHandler 為 SimplePropertyNamespaceHandler 對(duì)象;
其次,調(diào)用 SimplePropertyNamespaceHandler 對(duì)象對(duì)當(dāng)前的元素進(jìn)行解析;
可以看到,前面的解析并沒(méi)有什么特殊的,從元素 p:spouse-ref="jane" 中解析得到 propery name: spouse-ref,property value: jane;但是后續(xù)解析,比較特殊,需要處理 REF_SUFFIX 的情況了,也就是當(dāng) property name 的后綴為 -ref 的情況,表示該 attribute 是一個(gè) ref-bean 屬性,其屬性值引用的是其它的 bean 實(shí)例,所以呢,這里將其 property value 封裝為了一個(gè) RuntimeBeanReference
對(duì)象實(shí)例,表示將來(lái)在解析該 property value 為 Java Object 的時(shí)候,需要去初始化其引用的 bean 實(shí)例 jane,然后注入到當(dāng)前的 property value 中;
? 最后,注冊(cè) bean definition;
見(jiàn) step 1.3.2 register.registerBeanDefinition(beanName, beanDefinition),register 就是當(dāng)前的 bean factory 實(shí)例,通過(guò)將 bean name 和 bean definition 以鍵值對(duì)的方式在當(dāng)前的 bean factory 中進(jìn)行注冊(cè);這樣,我們就可以通過(guò) bean 的名字,得到其對(duì)應(yīng)的 bean definition 對(duì)象了;
? 寫(xiě)在該小節(jié)最后,
我們也可以自定義某個(gè) element 或者 element attribute,并且定義與之相關(guān)的 namespace 和 namespace handler,這樣,就可以使得 Spring 容器解析自定義的元素;類似于 dubbo 配置中所使用的 <dubbo /> 自定義元素那樣;
此步驟對(duì)應(yīng) register bean definitions process 步驟中的 step 1.3.3.2
該小節(jié)我將試圖使用一個(gè)常用的 custom element: <context:component-scan/> 來(lái)梳理整個(gè)流程;
繼續(xù) parse custom element process 章節(jié)中所使用到的例子,<context:component-scan/> 來(lái)分析該流程,
? 在開(kāi)始分析之前,看看 component-scan 元素長(zhǎng)什么樣,
注意,component-scan element 本身包含 annotation-config attribute;
? 流程分析
首先,根據(jù) element name: component-scan 找到對(duì)應(yīng)的 BeanDefinitionParser,在 ContextNamespaceHandler 初始化的時(shí)候,便初始化設(shè)置好 8 對(duì)內(nèi)置的 element name 與 parsers 的鍵值對(duì);這里,根據(jù)名字 component-scan 找到對(duì)應(yīng)的 parser ComponentScanBeanDefinitionParser 對(duì)象;
其次,使用 ComponentScanBeanDefinitionParser 對(duì)象開(kāi)始解析工作,
首先,解析 <context:component-scan base-package="org.shangyang"/> 得到 basePcakges String[] 對(duì)象;
其次,初始化得到 ClassPathBeanDefinitiionScanner 對(duì)象實(shí)例 scanner,然后調(diào)用 scanner.doScan 方法進(jìn)入 [do scan 流程](#do-scan 流程),該流程中將會(huì)遍歷 base package 中所包含的所有 .class 文件,解析之,并生成相應(yīng)的 bean definitions;另外在這個(gè)流程中,還要注意的是,最后會(huì)將 bean definitions 在當(dāng)前的 bean factory 對(duì)象中進(jìn)行注冊(cè);
這里主要介紹上一個(gè)小節(jié)中 #2 步驟中所提到的 do scan 流程步驟,對(duì)應(yīng) parse element by ContextNamespaceHandler 流程圖中的 step 1.2.3 scanner.doScan;
? 先來(lái)看看 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. 從當(dāng)前用戶自定的 classpath 子路徑中,通過(guò) regex 查詢到所有的所匹配的 resources;要特別注意的是,
// 這里為什么不直接通過(guò) Class Loader 去獲取 classes 來(lái)進(jìn)行判斷? 因?yàn)檫@樣的話就相當(dāng)于是加載了 Class Type,而 Class Type 的加載過(guò)程是通過(guò) Spring 容器嚴(yán)格控制的,是不允許隨隨便便加載的
// 所以,取而代之,使用一個(gè) File Resource 去讀取相關(guān)的字節(jié)碼,從字節(jié)碼中去解析........
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
//2. 依次遍歷用戶定義的 bean Class 對(duì)象
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
// 將從字節(jié)碼中獲取到的相關(guān) annotation(@Service) 以及 FileSystemResource 對(duì)象保存在 metadataReader 當(dāng)中;
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);
這一步通過(guò)遞歸搜索 base package 目錄下的所有 .class 文件,并將其字節(jié)碼封裝成 Resource[] 對(duì)象;上面的注釋解釋得非常清楚了,這里封裝的是 .class 文件的字節(jié)碼,而非 class type;除了注解中所描述的,這里再引申說(shuō)明下,這里為什么不直接加載其 Class Type 還有一個(gè)原因就是當(dāng) Spring 在加載 Class Type 的時(shí)候,很有可能在該 Class Type 上配置了 AOP,通過(guò) ASM 字節(jié)碼技術(shù)去修改原有的字節(jié)碼以后,再加入 Class Loader 中;所以,之類不能直接去解析 Class Type,而只能通過(guò)字節(jié)碼的方式去解析;
這一步同樣告誡
我們,在使用 Spring 容器來(lái)開(kāi)發(fā)應(yīng)用的時(shí)候,開(kāi)發(fā)者不要隨隨便便的自行加載 Class Type 到容器中
,因?yàn)橛锌赡茉诩虞d Class Type 之前需要通過(guò) Spring 容器的 ASM AOP 進(jìn)行字節(jié)碼的修改以后再加載;
代碼第 23 行
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
解析當(dāng)前的 .class 字節(jié)碼,解析出對(duì)應(yīng)的 annotation,比如 @Service,并將其協(xié)同 FileSystemResource 對(duì)象一同保存到 metadataReader 對(duì)象中;
代碼第 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;
}
既是從當(dāng)前的 metadataReader 中去判斷是否存在 1. @Component 2. @ManagedBean 3. @Named 三種注解中的一種,如果是,則進(jìn)入下面的流程
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
...
}
? 依次處理并注冊(cè)返回的 candidates
該步驟從流程圖 parse element by ContextNamespaceHandler 中的 step 1.2.3.2 開(kāi)始,主要做了如下幾件事情,
/**
* 因?yàn)橥ㄟ^(guò) @Component、@Serivce 等注解的方式不會(huì)像 xml-based 配置那樣提供了一個(gè) name 的標(biāo)簽,可以指定 bean name;所以,這里需要去單獨(dú)為其生成一個(gè);
*/
@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. 里面的實(shí)現(xiàn)邏輯就是通過(guò)將 Class Name 的首字母大寫(xiě)編程小寫(xiě),然后返回;
return buildDefaultBeanName(definition, registry);
}
通常情況下,是將類名的首字母進(jìn)行小寫(xiě)并返回;對(duì)應(yīng) step 1.2.2.3.3
該步驟從流程圖 parse element by ContextNamespaceHandler 的 step 1.2.4.2 registerAnnotationConfigProcessors 開(kāi)始,將會(huì)依次注冊(cè)由如下 post-processor class 對(duì)象所對(duì)應(yīng)的 post-processor-bean-definitions,
注意,這里都是通過(guò) Class 對(duì)象注冊(cè)的,并非注冊(cè)的實(shí)例化對(duì)象,下面,我們來(lái)簡(jiǎn)單分析一下注冊(cè)相關(guān)的源碼,以注冊(cè) 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); // 注冊(cè) bean definition
return new BeanDefinitionHolder(definition, beanName);
}
這一步將 AutowiredAnnotationBeanPostProcessor 所對(duì)應(yīng)的 bean definition 注入了當(dāng)前的 bean factory 當(dāng)中;
AutowiredAnnotationBeanPostProcessor 提供了 @Autowired 注解注入機(jī)制的實(shí)現(xiàn),詳情參考 AutowiredAnnotationBeanPostProcessor 章節(jié);
通過(guò)上述的分析,可以清晰的看到,bean definition 的作用是什么,就是通過(guò) bean definition 中的描述去限定通過(guò) Class Type 實(shí)例化得到 instance 的業(yè)務(wù)規(guī)則,我們看看由 do scan 流程 所生成的 annotation-bean-definition<ScannedGenericBeanDefinition> 對(duì)象,
{% asset_img debug-scanned-generic-bean-definition.png %}
可以看到,當(dāng)我們?cè)诤罄m(xù)要根據(jù)該 annotation-bean-definition 得到一個(gè) DogService 實(shí)例的時(shí)候,所要遵循的業(yè)務(wù)規(guī)則,如下所示,
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]
不過(guò),要注意,這里所得到的 ScannedGenericBeanDefinition 實(shí)例,同樣沒(méi)有真正去加載 org.shangyang.spring.container.DogService Class Type 到容器中,而只是將 class name 字符串
賦值給了 ScannedGenericBeanDefinition.beanClass,言外之意,將來(lái)在加載 Class Type 到容器中的時(shí)候,或許與實(shí)例化 instance 一樣也要根據(jù) bean definitions 中的規(guī)則來(lái)限定其加載行為,目前我所能夠想到的與其相關(guān)的就是 ASM 字節(jié)碼技術(shù),可以在 bean definition 中定義 ASM 字節(jié)碼修改規(guī)則,來(lái)控制相關(guān) Class Type 的加載行為;
本文轉(zhuǎn)載自本人的私人博客,傷神的博客 http://www.shangyang.me/2017/04/07/spring-core-container-sourcecode-analysis-register-bean-definitions/
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。