相關背景及資源:html
曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java
曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解node
曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下git
曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?spring
曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取beanjson
工程結構圖:數組
此次,咱們打算講一下,spring啓動時,是怎麼去讀取xml文件的,bean的解析部分可能暫時涉及不到,由於放到一塊兒,內容就太多了,具體再看吧。dom
給ClassPathXmlApplicationContext
設置xml文件的路徑ide
refresh
內部的beanFactory
,其實這時候BeanFactory
都還沒建立,會先創DefaultListableBeanFactory
ClassPathXmlApplicationContext
調用其loadBeanDefinitions
,將新建DefaultListableBeanFactory
做爲參數傳入
ClassPathXmlApplicationContext
內部會持有一個XmlBeanDefinitionReader
,且XmlBeanDefinitionReader
內部是持有以前建立的DefaultListableBeanFactory
的,這時候就簡單了,XmlBeanDefinitionReader
負責讀取xml,將bean definition
解析出來,丟給DefaultListableBeanFactory
,此時,XmlBeanDefinitionReader
就算完成了,退出歷史舞臺
上面第四步完成後,DefaultListableBeanFactory
裏面其實一個業務bean都沒有,只有一堆的 bean definition
,後面ClassPathXmlApplicationContext
直接去實例化那些須要在啓動過程當中實例化的bean。
當前,這中間還涉及到使用beanDefinitionPostProcessor
和beanPostProcessor
對beanFactory進行處理,這都是後話了。
此次,咱們主要講的部分是,第4步。
位置:org.springframework.context.support.AbstractRefreshableApplicationContext @Override protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //建立一個DefaultListableBeanFactory DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); // 加載 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
上面調用了
AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { // 建立一個從xml讀取beanDefinition的讀取器 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 配置環境 beanDefinitionReader.setEnvironment(this.getEnvironment()); // 配置資源loader,通常就是classpathx下去獲取xml beanDefinitionReader.setResourceLoader(this); // xml解析的解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); // 核心方法,使用beanDefinitionReader去解析xml,並將解析後的bean definition放到beanFactory loadBeanDefinitions(beanDefinitionReader); }
// xml解析的解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
這個類實現的接口就是jdk裏面的org.xml.sax.EntityResolver
,這個接口,只有一個方法,主要負責xml裏,外部實體的解析:
public interface EntityResolver { public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException; }
你們可能不太明白,咱們看看怎麼實現的吧:
public class DelegatingEntityResolver implements EntityResolver { /** Suffix for DTD files */ public static final String DTD_SUFFIX = ".dtd"; /** Suffix for schema definition files */ public static final String XSD_SUFFIX = ".xsd"; private final EntityResolver dtdResolver; private final EntityResolver schemaResolver; public DelegatingEntityResolver(ClassLoader classLoader) { this.dtdResolver = new BeansDtdResolver(); this.schemaResolver = new PluggableSchemaResolver(classLoader); } // 主要看這裏,感受就是對咱們xml裏面的那堆xsd進行解析 @override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { if (systemId != null) { if (systemId.endsWith(DTD_SUFFIX)) { return this.dtdResolver.resolveEntity(publicId, systemId); } else if (systemId.endsWith(XSD_SUFFIX)) { return this.schemaResolver.resolveEntity(publicId, systemId); } } return null; }
舉個例子,咱們xml裏通常不是有以下代碼嗎:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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">
上面的代碼,應該就是去獲取和解析上面這裏的xsd
,方便進行語法校驗的。畢竟,這個xml咱們也不能隨便亂寫吧,好比,根元素就是
接下來,咱們仍是趕忙切入正題吧,看看XmlBeanDefinitionReader
是怎麼解析xml的。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); //這個方法還在:AbstractXmlApplicationContext,獲取資源位置,傳給 XmlBeanDefinitionReader if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
通過幾個簡單跳轉,進入下面的方法:
org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set<org.springframework.core.io.Resource>) public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader instanceof ResourcePatternResolver) { try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); // 從資源數組裏load bean definition int loadCount = loadBeanDefinitions(resources); return loadCount; } } }
這裏插一句,看看其類圖:
總的來講,相似於模板設計模式,一些通用的邏輯和流程,放在AbstractBeanDefinitionReader
,具體的解析啊,都是放在子類實現。
咱們這裏也能夠看到,其實現了一個接口,BeanDefinitionReader
:
package org.springframework.beans.factory.support; public interface BeanDefinitionReader { // 爲何須要這個,由於讀取到bean definition後,須要存到這個裏面去;若是不提供這個,我讀了往哪放 BeanDefinitionRegistry getRegistry(); //資源加載器,加載xml之類,固然,做爲一個接口,資源是能夠來自於任何地方 ResourceLoader getResourceLoader(); //獲取classloader ClassLoader getBeanClassLoader(); // beanName生成器 BeanNameGenerator getBeanNameGenerator(); // 從資源load bean definition int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException; }
咱們切回前面,AbstractBeanDefinitionReader
實現了大部分方法,除了下面這個:
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
由於,它畢竟只是個抽象類,不負責幹活啊;並且,爲了可以從不一樣的resource讀取,這個也理應交給子類。
好比,咱們這裏的XmlBeanDefinitionReader
就是負責從xml文件讀取;我以前的文章裏,也演示瞭如何從json讀取,也是自定義了一個AbstractBeanDefinitionReader
的子類。
接着上面的方法往下走,立刻就進入到了:
位置:org.springframework.beans.factory.xml.XmlBeanDefinitionReader,插入的參數,就是咱們的xml public int loadBeanDefinitions(EncodedResource encodedResource) { ... try { // 讀取xml文件爲文件流 InputStream inputStream = encodedResource.getResource().getInputStream(); try { //讀取爲xml資源 InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 解析bean definition去 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } } }
這裏,提一句InputSource
,這個類的全路徑爲:org.xml.sax.InputSource
,是jdk裏的類。
包名裏包括了xml,知道大概是xml相關的類了,包名也包含了sax,大概知道是基於sax事件解析模型。
這方面我懂得也很少,回頭能夠單獨講解。咱們繼續正文:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) { try { int validationMode = getValidationModeForResource(resource); // 反正org.w3c.dom.Document也是jdk的類,具體無論 Document doc = this.documentLoader.loadDocument( inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); // 下邊就開始正題了 return registerBeanDefinitions(doc, resource); } }
關於documentLoader.loadDocument
,你們只要簡單知道,是進行初步的解析就好了,主要是基於xsd、dtd等,進行語法檢查等。
我這裏的堆棧,你們看下:
這裏,先不深究xml解析的東西(主要是我也不怎麼會啊,哈哈哈)
接着走:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
看得出來,XmlBeanDefinitionReader
可能以爲工做繁重,因而將具體的工做,交給了BeanDefinitionDocumentReader
。
public interface BeanDefinitionDocumentReader { void setEnvironment(Environment environment); void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) throws BeanDefinitionStoreException; }
核心方法確定是
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
兩個參數,Document
咱們理解,就是表明xml文件;
XmlReaderContext
是啥,看樣子是個上下文,上下文,通常來講,就是個大雜燴,把須要用到的都會放在裏面。
咱們看看放了些啥:
public class XmlReaderContext extends ReaderContext { // 以前的核心類,把本身傳進來了 private final XmlBeanDefinitionReader reader; // org.springframework.beans.factory.xml.NamespaceHandlerResolver,這個也是核心類! private final NamespaceHandlerResolver namespaceHandlerResolver; } public class ReaderContext { //xml資源自己 private final Resource resource; //盲猜是中間處理報錯後,報告問題 private final ProblemReporter problemReporter; // event/listener機制吧,方便擴展 private final ReaderEventListener eventListener;` // 跳過 private final SourceExtractor sourceExtractor; }
看完了這個上下文的定義,要知道,它是做爲一個參數,傳給:
org.springframework.beans.factory.xml.BeanDefinitionDocumentReader void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) throws BeanDefinitionStoreException;
那,這個參數怎麼構造的呢?
那,咱們還得切回前面的代碼:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); // 這裏,調用createReaderContext(resource)建立上下文 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
/** * Create the {@link XmlReaderContext} to pass over to the document reader. */ protected XmlReaderContext createReaderContext(Resource resource) { if (this.namespaceHandlerResolver == null) { // 建立一個namespacehandler this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver(); } return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, this.namespaceHandlerResolver); }
這裏太核心了,咱們看看namespaceHandlerResolver
是幹嗎的:
public interface NamespaceHandlerResolver { /** * Resolve the namespace URI and return the located {@link NamespaceHandler} * implementation. * @param namespaceUri the relevant namespace URI * @return the located {@link NamespaceHandler} (may be {@code null}) */ // 好比解析xml時,咱們可能有<bean>,這個是默認命名空間,其namespace就是<beans>;若是是<context:component-scan>,這裏的namespace,就是context NamespaceHandler resolve(String namespaceUri); }
這個類的用途,就是根據傳入的namespace,找到對應的handler。
你們能夠去spring-beans.jar包裏的META-INF/spring.handlers
找一找,這個文件打開後,內容以下:
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
也能夠再去spring-context.jar裏找找,裏面也有這個文件:
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
我列了個表格:
namespace | handler |
---|---|
http://www.springframework.org/schema/c | org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler |
http://www.springframework.org/schema/p | org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler |
http://www.springframework.org/schema/util | org.springframework.beans.factory.xml.UtilNamespaceHandler |
http://www.springframework.org/schema/context | org.springframework.context.config.ContextNamespaceHandler |
http://www.springframework.org/schema/task | org.springframework.scheduling.config.TaskNamespaceHandler |
http://www.springframework.org/schema/cache | org.springframework.cache.config.CacheNamespaceHandler |
比較重要的,我都列在上面了,剩下的jee
/lang
,我沒用過,不知道你們用過沒,先跳過吧。
你們可能也有感到奇怪的地方,怎麼沒有
接着看核心邏輯:
DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createHelper(this.readerContext, root, parent); preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
恩。。。咱們已經快瘋了,怎麼又出來一個類,DefaultBeanDefinitionDocumentReader
看來也是以爲本身工做壓力太大了吧,這裏又弄了個BeanDefinitionParserDelegate
。
這個類,沒實現什麼接口,就是個大雜燴,方法多的一批,主要是進行具體的解析工做,你們能夠看看裏面定義的字段:
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"; public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; "; /** * Value of a T/F attribute that represents true. * Anything else represents false. Case seNsItive. */ public static final String TRUE_VALUE = "true"; public static final String FALSE_VALUE = "false"; public static final String DEFAULT_VALUE = "default"; public static final String DESCRIPTION_ELEMENT = "description"; public static final String AUTOWIRE_NO_VALUE = "no"; public static final String AUTOWIRE_BY_NAME_VALUE = "byName"; public static final String AUTOWIRE_BY_TYPE_VALUE = "byType"; public static final String AUTOWIRE_CONSTRUCTOR_VALUE = "constructor"; public static final String AUTOWIRE_AUTODETECT_VALUE = "autodetect"; public static final String DEPENDENCY_CHECK_ALL_ATTRIBUTE_VALUE = "all"; public static final String DEPENDENCY_CHECK_SIMPLE_ATTRIBUTE_VALUE = "simple"; public static final String DEPENDENCY_CHECK_OBJECTS_ATTRIBUTE_VALUE = "objects"; public static final String NAME_ATTRIBUTE = "name"; public static final String BEAN_ELEMENT = "bean"; public static final String META_ELEMENT = "meta"; public static final String ID_ATTRIBUTE = "id"; public static final String PARENT_ATTRIBUTE = "parent"; public static final String CLASS_ATTRIBUTE = "class"; public static final String ABSTRACT_ATTRIBUTE = "abstract"; public static final String SCOPE_ATTRIBUTE = "scope"; public static final String SINGLETON_ATTRIBUTE = "singleton"; public static final String LAZY_INIT_ATTRIBUTE = "lazy-init"; public static final String AUTOWIRE_ATTRIBUTE = "autowire"; public static final String AUTOWIRE_CANDIDATE_ATTRIBUTE = "autowire-candidate"; public static final String PRIMARY_ATTRIBUTE = "primary"; public static final String DEPENDENCY_CHECK_ATTRIBUTE = "dependency-check"; public static final String DEPENDS_ON_ATTRIBUTE = "depends-on"; public static final String INIT_METHOD_ATTRIBUTE = "init-method"; public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method"; public static final String FACTORY_METHOD_ATTRIBUTE = "factory-method"; public static final String FACTORY_BEAN_ATTRIBUTE = "factory-bean"; public static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg"; public static final String INDEX_ATTRIBUTE = "index"; public static final String TYPE_ATTRIBUTE = "type"; public static final String VALUE_TYPE_ATTRIBUTE = "value-type"; public static final String KEY_TYPE_ATTRIBUTE = "key-type"; public static final String PROPERTY_ELEMENT = "property"; public static final String REF_ATTRIBUTE = "ref"; public static final String VALUE_ATTRIBUTE = "value"; public static final String LOOKUP_METHOD_ELEMENT = "lookup-method"; public static final String REPLACED_METHOD_ELEMENT = "replaced-method"; public static final String REPLACER_ATTRIBUTE = "replacer"; public static final String ARG_TYPE_ELEMENT = "arg-type"; public static final String ARG_TYPE_MATCH_ATTRIBUTE = "match"; public static final String REF_ELEMENT = "ref"; public static final String IDREF_ELEMENT = "idref"; public static final String BEAN_REF_ATTRIBUTE = "bean"; public static final String LOCAL_REF_ATTRIBUTE = "local"; ...
看出來了麼,主要都是xml裏面的那些元素的名稱。
裏面的方法,不少,咱們用到了再說。
咱們繼續回到主線任務:
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions /** * 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) { // 通常來講,咱們的根節點都是<beans>,這個是默認namespace的 if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); // 遍歷xml <beans>下的每一個元素 for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; // 判斷元素是否是默認命名空間的,好比<bean>是,<context:component-scan>不是 if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { //<context:component-scan>,<aop:xxxx>走這邊 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
這裏,判斷一個元素是否是默認命名空間,具體怎麼作的呢:
BeanDefinitionParserDelegate#isDefaultNamespace(org.w3c.dom.Node) public boolean isDefaultNamespace(Node node) { //調用重載方法 return isDefaultNamespace(getNamespaceURI(node)); } public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"; public boolean isDefaultNamespace(String namespaceUri) { return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri)); }
DefaultBeanDefinitionDocumentReader#parseDefaultElement private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
第一次發現,原來默認命名空間下,才這麼幾個元素:
import、alias、bean、(NESTED_BEANS_ELEMENT)beans
具體的解析放到下講,這講內容已經夠多了。
主要是處理:aop、context
等非默認namespace
。
BeanDefinitionParserDelegate public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { String namespaceUri = getNamespaceURI(ele); // 這裏,就和前面串起來了,根據namespace找handler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
咱們挑一個你們最熟悉的org.springframework.context.config.ContextNamespaceHandler
,你們先看看:
public class ContextNamespaceHandler extends NamespaceHandlerSupport { public void init() { // 這個也熟悉吧 registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser()); registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser()); registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); // 這個熟悉吧 registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser()); registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser()); registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser()); } }
總之,到了這裏,就是根據具體的元素,找對應的處理器了。這些都後面再說了。內容太多了。
你們能夠回頭再去看看第二章的總體流程,會不會清晰一些了呢?
主要是幾個核心類:
XmlBeanDefinitionReader
BeanDefinitionDocumentReader
XmlReaderContext
namespaceHandlerResolver
BeanDefinitionParserDelegate
內容有點多,你們不要慌,咱們後面還會進一步講解的。以爲有幫助的話,點個贊哦。