曹工說Spring Boot原始碼(6)-- Spring怎麼從xml檔案裡解析bean的
寫在前面的話
相關背景及資源:
曹工說Spring Boot原始碼(1)-- Bean Definition到底是什麼,附spring思維導圖分享
曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解
曹工說Spring Boot原始碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下
曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?
曹工說Spring Boot原始碼(5)-- 怎麼從properties檔案讀取bean
工程程式碼地址 思維導圖地址
工程結構圖:
整體流程
這次,我們打算講一下,spring啟動時,是怎麼去讀取xml檔案的,bean的解析部分可能暫時涉及不到,因為放到一起,內容就太多了,具體再看吧。
給
ClassPathXmlApplicationContext
設定xml檔案的路徑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中xsd、dtd解析器
// 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的。
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;
}
}
}
XmlBeanDefinitionReader類圖
這裡插一句,看看其類圖:
總的來說,類似於模板設計模式,一些通用的邏輯和流程,放在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
的子類。
讀取xml檔案為InputSource
接著上面的方法往下走,馬上就進入到了:
位置: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;
}
BeanDefinitionDocumentReader
看得出來,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
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
這裡太核心了,我們看看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;
}
BeanDefinitionParserDelegate
恩。。。我們已經快瘋了,怎麼又出來一個類,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));
}
預設namespace時的邏輯
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
具體的解析放到下講,這講內容已經夠多了。
非預設namespace時的邏輯
主要是處理: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
內容有點多,大家不要慌,我們後面還會進一步講解的。覺得有幫助的話,點個贊哦