本文已收錄 【修煉內功】躍遷之路
寫在最前~距spring-framework開篇的那篇文章已經一個月了,若是再照這樣的速度下去,這個flag估計大機率又要呵呵~
最近發生了一些事情讓我迷茫於應該堅持什麼,爲何還要癡迷於工做兩三年本就應該掌握的東西上~
‘年’(夕獸)就要來了,總要準備點兒什麼纔能有資本‘除夕’不是~
言歸正傳,切入正題!html
上一篇介紹了Spring中的Resource
,一個相似於Linux中「一切皆文件」的概念,屏蔽了對不一樣類型資源操做的差別性,本篇繼續深刻,但必定沒有各位想象或者期待中的那麼深java
平時接觸Spring必定離不開兩個基本的核心概念:容器和控制反轉,然而這兩個概念都不是這篇文章要展開討論的;平時使用Spring都是從各類ApplicationContext開始(如最多見的ClassPathXmlApplicationContext
或者SpringBoot中的SpringApplication
),然而,ApplicationContext也不是本篇的重點~(大寫調皮,後文詳解)node
在使用Spring的過程當中,咱們會(經過xml <bean class='xxx'></bean>
、註解@Bean
、@Component
等等方式)建立大量的<u>bean</u>,衆所周知,bean是被容器所管理的,bean之間的依賴注入依靠的是控制反轉,那在咱們使用bean以前,其是如何被註冊到容器中的呢?git
要解釋這個問題其實很簡單:step 1 解析;step 2 註冊。本篇,咱們將重心放在bean的解析上(但不包含全部方式的解析)github
Spring在註冊bean以前的解析方式有不少種,但無外乎三大類:1. 文件形式的配置;2. 註解形式的配置;3. 硬編碼方式。本篇只談第一類中的一種形式 -- xml配置文件的解析spring
爲何是xml這種千年老古董,由於市面上的書歷來都只講xml配置的解析啊(手動調皮),以上爲玩笑(如下嚴肅臉),由於xml文件對於理解bean的解析更爲容易,在註解大行其道的當下,但願我能堅持到某一天來撰寫一篇文章解釋註解在bean解析、註冊上的運行機制segmentfault
除了一些老項目(不可抗拒的歷史緣由)或者特殊場景依然在使用xml配置外,我相信大多數的程序猿都是向前看的,畢竟Spring(Boot)也在力推零xml配置瀏覽器
Absolutely no code generation and no requirement for XML configuration.
因此,我但願各位不要將重點放在如何解析xml配置上,而是將其做爲「藥引子」,引出其背後更爲重要的概念(BeanDefinition
、BeanFactory
、BeanRegistry
)網絡
基於xml配置的ApplicationContext常見的有ClassPathXmlApplicationContext
、FileSystemXmlApplicationContext
(見名識意),基於xml配置的BeanFactory則是XmlBeanFactory
(ApplicationContext與BeanFactory的關係會放到後文中),而全部這些在解析bean的時候使用的都是XmlBeanDefinitionReader
(具體如何使用會在之後的篇幅中介紹,本篇請將重點暫時放在BeanDefinitionReader上)
XmlBeanDefinitionReader
實現自BeanDefinitionReader
,而在BeanDefinitionReader
的實現裏,除了XmlBeanDefinitionReader
以外還有PropertiesBeanDefinitionReader
及GroovyBeanDefinitionReader
,一個解析properties文件、一個解析groovy腳本,因爲過於小衆、原理相通,此處不表(若是諸君有興趣及能力,能夠開發本身的YamlBeanDefinitionReader或KotlinBeanDefinitionReader等也未必不可)
看似複雜的一張類關係圖,理解起來其實並不複雜
BeanDefinitionReader
定義了一系列bean初始化的接口,如下列出幾個比較直觀的
public interface BeanDefinitionReader { // 加載單個配置文件 int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; // 加載多個配置文件 int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException; }
接口中定義了針對單個配置文件及多個配置文件的加載方法
Q:類名明明是 Reader,但方法名爲何是 load而不是 parse(加載和解析貌似不是一個概念)?
AbstractBeanDefinitionReader
實現了公共的處理邏輯
XmlBeanDefinitionReader
在解析的過程當中主要藉助了兩種接口,DocumentLoader
及BeanDefinitionDocumentReader
DocumentLoader
主要負責xml的驗證和讀取(默認實現爲DefaultDocumentLoader
)BeanDefinitionDocumentReader
主要負責Document的解析和註冊(默認實現爲DefaultBeanDefinitionDocumentReader
)
若是以爲上圖過於簡單、不夠細節,能夠參考下圖(因爲Spring源碼的複雜性,依然不能覆蓋全部的細節)
BeanDefinition
、BeanDefinitionHolder
)BeanDefinitionRegistry
、AliasRegistry
)以上,引出了幾個重要的新概念(BeanDefinition
、BeanDefinitionHolder
、BeanFactory
、BeanRegistry
),客官莫急,待我慢慢道來
xml的驗證和讀取能夠跟蹤到代碼XmlBeanDefinitionReader#doLoadDocument
中
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
documentLoader.loadDocument
內部實現則是常規的xml文件到Document的讀取邏輯,這裏簡要介紹一下EntityResolver
通常Spring配置文件以下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="" class=""> <property name="" value=""/> <property name="" ref=""/> </bean> </beans>
在解析以前須要對xml的內容進行校驗,校驗須要依賴XSD或DTD聲明文件,如上http://www.springframework.or...(瀏覽器中可直接打開)
這裏有一個問題,基於Spring的應用不可能所有運行在可連通互聯網的環境中,若是沒有網絡環境,基於xml配置的Spring初始化均會失敗
EntityResolver
的做用是提供一個尋找本地XSD或DTD聲明文件的方法,具體的過程再也不詳解,能夠跟到XmlBeanDefinitionReader#getEntityResolver
查看
spring-beans的聲明文件都可在spring-beans模塊中找到
至此,可在離線的環境中獲取XSD或DTD文件,以驗證並解析xml配置文件,爲後續bean的解析作足準備
Document的解析、註冊主要使用了BeanDefinitionDocumentReader
(由DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
跟入)
在進行bean的解析以前,首先須要匹配==profiles==
▷ 什麼是profiles?
https://docs.spring.io/spring...Spring Profiles provide a way to segregate parts of your application configuration and make it be available only in certain environments.
簡單來說,咱們能夠爲不一樣的環境設置不一樣的配置,有選擇性的讓Spring加載
▷ 如何使用profiles?
xml文件中
<beans profile="dev"></beans>
SpringBoot properties文件名中
application-dev.properties
SpringBoot yaml文件中
server: port: 80 --- spring: profiles: dev server: port: 8080
註解中
@Bean @Profile({"dev"}) public MyBean myDevBean() { return new MyDevBean(); } @Bean @Profile({"production"}) public MyBean myProductionBean() { return new MyProductionBean(); }
等等方式...
▷ 如何激活profile?
java -jar xxx.jar -Dspring.profiles.active=dev
profile的配置會被讀取並記錄在Environment
中,在進行bean的解析以前,第一步須要判斷當前beans的profile是否與Environment
中記錄的profile相匹配,只有匹配的纔會被加載,並進入解析、註冊流程,這也便作到了配置的環境隔離(具體的處理過程參見DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
)
在spring-beans模塊的範疇內,默認的命名空間只包含四種標籤:<beans>
、<bean>
、<alias>
、<import>
,但在實際使用過程當中,咱們用到的遠遠不止這三種,好比<tx:advice>
、<aop:config>
等等,這些標籤的命名空間並不在==beans==內,前者的命名空間爲==tx==,XSD文件在spring-tx.jar!org/springframework/transaction/config/spring-tx.xsd3,後者的命名空間爲==aop==,XSD文件在spring-tx.jar!org/springframework/aop/config/spring-aop.xsd4
除了默認命名空間內的標籤外,其餘命名空間的標籤都是以一種擴展(自定義)的形式存在
// DefaultBeanDefinitionDocumentReader.java /** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */ protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { // 默認命名空間的解析 NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { // 默認命名空間(標籤)的解析 parseDefaultElement(ele, delegate); } else { // 默認命名空間(標籤)中,出現了自定義命名空間(標籤) delegate.parseCustomElement(ele); } } } } else { // 自定義命名空間(標籤)的解析 delegate.parseCustomElement(root); } }
不管默認標籤仍是自定義標籤,實際的標籤解析過程均由BeanDefinitionParserDelegate
提供
默認命名空間的標籤無外乎<bean>
、<alias>
、<import>
(以及<beans>
嵌套)
// DefualtBeanDefinitionDocumentReader.java private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // 解析 <import> importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 解析 <alias> processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // 解析 <bean> processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // 遞歸解析 <beans> 嵌套 doRegisterBeanDefinitions(ele); } }
對於<import>
標籤,解析出location,處理佔位符,遞歸調用loadBeanDefinitions
進行解析註冊
對於<alias>
標籤,解析出bean的name和alias,並註冊(AliasRegistry
)
對於<bean>
標籤,狀況則會複雜不少,但也無外乎<bean>
屬性解析、<constructor-arg>
解析、<property>
解析等等
具體的解析細節再也不贅述,你們能夠自行查看源碼
// BeanDefinitionParserDelegate#parseBeanDefinitionElement try { // 建立BeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent); // <bean>屬性 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); // <description> bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // <meta> parseMetaElements(ele, bd); // <lookup-method> parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // <replaced-method> parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // <constructor-arg> parseConstructorArgElements(ele, bd); // <property> parsePropertyElements(ele, bd); // <qualifier> parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; }
標籤解析以後的結果存放到了那裏?BeanDefinition
!
BeanDefinition
只是bean的定義,存放構造該bean實例所須要的元信息,其中包含你能想到的一切有關bean的屬性信息
// BeanDefinition.java public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { // class name String getBeanClassName(); // 構造函數 ConstructorArgumentValues getConstructorArgumentValues(); // 屬性 MutablePropertyValues getPropertyValues(); // 初始化的方法名 String getInitMethodName(); // 銷燬的方法名 String getDestroyMethodName(); // ... }
除此以外,還有不少的輔助類,如
RuntimeBeanReference
存儲ref
信息TypedStringValue
存儲value
信息ManagedMap
存儲<map>
信息ManagedList
儲存<list>
信息至此就結束了麼?固然不是,咱們在配置bean的時候通常都會使用id來指定beanName,但若是沒有指定id呢?
<bean id="myBean" name="aliasBean" class=""></bean>
此時,則會使用BeanNameGenerator
(默認DefaultBeanNameGenerator
)生成默認的beanName(參見BeanDefinitionParserDelegate#parseBeanDefinitionElement
)
隨後,會將beanDefinition、beanName及別名alias一同包裝進BeanDefinitionHolder
BeanDefinitionHolder
的定義只是簡單捆綁了以上三者的關係
public class BeanDefinitionHolder implements BeanMetadataElement { // bean definition private final BeanDefinition beanDefinition; // bean name private final String beanName; // 別名alias private final String[] aliases; }
最後,將該BeanDefinition進行註冊(BeanDefinitionRegistry
)
// DefaultBeanDefinitionDocumentReader.java protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 解析BeanDefinitionHolder BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { // 註冊BeanDefinition BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); // 發送事件 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
Spring提供了一種能力,來擴展並自定義xml配置文件中的標籤
自定義標籤的實現須要依賴:
BeanDefinitionParser
實現,用來解析自定義標籤NamespaceHandlerSupport
實現,用來註冊自定義標籤解析器spring.handlers
及spring.schemas
,用來發現自定義標籤註冊器(NamespaceHandlerSupport
)及XSD文件BeanDefinitionParser
的定義一樣很簡單,將一個Document下的Element解析爲BeanDefinition(s),並註冊
public interface BeanDefinitionParser { BeanDefinition parse(Element element, ParserContext parserContext); }
以component-scan
爲例,其命名空間爲context
BeanDefinitionParser
的實現類爲ComponentScanBeanDefinitionParser
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser { @Override @Nullable public BeanDefinition parse(Element element, ParserContext parserContext) { String basePackage = /* 解析basePackage */; // 掃描BeanDefinition ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages); // 註冊BeanDefinition registerComponents(parserContext.getReaderContext(), beanDefinitions, element); return null; } }
其中定義瞭如何掃描指定package下的類,並根據註解註冊bean的邏輯
這裏有一個有趣的發現,經過配置文件註冊bean的實現類一般爲xxxBeanDefinitionLoader,而經過註解註冊bean的實現類一般爲xxxBeanDefinitionScanner
NamespaceHandlerSupport
的實現類爲ContextNamespaceHandler
public class ContextNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { // ... // 註冊<component-scan>的解析器 registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); // ... } }
spring.handlers文件部份內容爲
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
spring.schemas文件部份內容爲
http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context.xsd
同理,能夠找到<tx:advice>
、<aop:config>
等標籤的解析邏輯
回到BeanDefinitionParserDelegate
,細心的會發現,對於自定義標籤的解析邏輯,並無出現註冊的地方
// BeanDefinitionParserDelegate.java public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { // 獲取命名空間 String namespaceUri = getNamespaceURI(ele); // 獲取解析器 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); // 解析(Q: 並註冊?) return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
對於大多數自定義標籤而言,其都是自注冊的,即在BeanDefinitionParser.parse
中完成註冊的邏輯,如<component-scan>
,那對於<tx:advice>
之類的標籤是如何完成自注冊的?AbstractBeanDefinitionParser
!
// AbstractBeanDefinitionParser public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser { @Override @Nullable public final BeanDefinition parse(Element element, ParserContext parserContext) { // Element解析爲BeanDefinition AbstractBeanDefinition definition = parseInternal(element, parserContext); // 解析id及aliases,包裝爲BeanDefinitionHolder BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); // 註冊!!! registerBeanDefinition(holder, parserContext.getRegistry()); // 發送事件 if (shouldFireEvents()) { BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } return definition; } }
AbstractBeanDefinitionParser
實現了相似於ComponentScanBeanDefinitionParser
的邏輯,在解析出BeanDefinition後隨即將其註冊
上文中屢次提到了註冊
<alias>
標籤並註冊<bean>
標籤並註冊註冊的具體細節但願能和bean的初始化及獲取放到一塊兒來說,本篇簡單介紹並引出幾個概念
bean的註冊由xxxBeanDefinitionRegistry
實現,並非註冊bean的實例對象,而是註冊bean的元數據BeanDefinition
別名的註冊由xxxAliasRegistry
實現
bean的獲取由xxxBeanFactory
實現(包括bean的初始化、依賴注入等)
BeanDefinition的實現有多種,各中關係後續文章詳解
xxxBeanDefinitionRegistry
與xxxBeanFactory
的實現也是錯綜複雜,篇幅有限,統一放到後續文章中講解
XmlBeanDefinitionReader
負責xml配置的bean解析及註冊
DocumentLoader
負責xml文件的校驗及加載BeanDefinitionDocumentReader
負責bean的解析及註冊xml中的標籤分爲兩大類,默認標籤及自定義標籤,其解析邏輯不一樣
BeanDefinitionParserDelegate
提供統一入口BeanDefinitionParser
、NamespaceHandlerSupport
bean的元數據被存放在BeanDefinition
中,並同beanName、別名alias封裝在BeanDefinitionHolder
裏
xxxBeanDefinitionRegistry
實現xxxAliasRegistry
實現<component-scan>
),使用註解一樣能夠加載基於xml配置的bean(如@Import({"classpath:xxx.xml"})
),二者之間是如何相融合的ReaderContext#fireXXX
),事件在哪裏使用寫在最後~2019結束了,但願2020能有新的突破!