本來覺得,Spring 經過解析 bean 的配置,生成並註冊 bean defintions 的過程不太複雜,比較簡單,不用單獨開闢一篇博文來說述;可是當在分析前面兩個章節有關 @Autowired、@Component、@Service 註解的注入機制的時候,發現,若是沒有對有關 bean defintions 的解析和註冊機制完全弄明白,則很難弄清楚 annotation 在 Spring 容器中的底層運行機制;因此,本篇博文做者將試圖去弄清楚 Spring 容器內部是如何去解析 bean 配置並生成和註冊 bean definitions 的相關主流程;java
備註,本文是做者的原創做品,轉載請註明出處。node
➥ bean definitions 是什麼?spring
其實很簡單,就是 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>
能夠看到,上面有三個 elementapp
在配置文件 beans.xml 被 Spring 解析的過程當中,每個 element 將會被解析爲一個 bean definition 對象緩存在 Spring 容器中;ide
➥ 須要被描述爲 bean definitions 的配置對象主要分爲以下幾大類,源碼分析
➥ 最開始個人確是這麼認識 bean definitions 的,可是當我分析完有關 bean definitions 的相關邏輯和源碼之後,對其認識有了昇華,參考寫在最後;post
最好的分析源碼的方式,就是經過高屋建瓴,逐個擊破的方式;首先經過流程圖得到它的藍圖(頂層設計圖),而後再根據藍圖上的點逐個擊破;最後才能達到融會貫通,成竹在胸的境界;因此,這裏做者用這樣的方式帶你深刻剖析 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_;
而後經過 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") 的狀況,這裏不打算在這裏繼續深挖;
當完成上述三個步驟之後,將進入 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/>
➥ 後續,當前面的工做準備好了之後,來看看是如何解析 element 的?
首先,判斷 root 元素的 namespace 對應的是否是 default namespace,若不是,將進入 _step 1.3.3.3: parse custom element_;這裏咱們關注常規流程,既是當 root 元素的 namespace 是 default namespace 的流程;
遍歷 root 元素下的全部 element,
能夠看到,該流程中包含四個子流程,依次處理不一樣的 element 元素的狀況,其它三種都是比較特殊的狀況,咱們這裏,主要關注「解析 <bean/>" 元素的流程」
這裏,爲了可以儘可能的展現出解析 <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
常規步驟
attribute node 的修飾過程
假設,咱們當前的 attribute node 爲 _p:spouse-ref="jane"_,看看該屬性是如何被解析的,
RuntimeBeanReference
對象實例,表示未來在解析該 property value 爲 Java Object 的時候,須要去初始化其引用的 bean 實例 _jane_,而後注入到當前的 property value 中;➥ 最後,註冊 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 /> 自定義元素那樣;
此步驟對應 register bean definitions process 步驟中的 step 1.3.3.2
該小節我將試圖使用一個經常使用的 custom element: <context:component-scan/> 來梳理整個流程;
繼續 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 對象開始解析工做,
這裏主要介紹上一個小節中 #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 開始,主要作了以下幾件事情,
經過 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