Spring解密之XML解析與Bean注冊(cè)示例詳解
為什么開始看spring的源碼
半路轉(zhuǎn)行寫代碼快一年半了,從開始工作就在使用spring框架,雖然會(huì)用,會(huì)搭框架,但是很多時(shí)候不懂背后的原理,比如:spring是怎樣控制事務(wù)的,springmvc是怎樣處理請(qǐng)求的,aop是如何實(shí)現(xiàn)的...這讓人感覺非常不踏實(shí),那就開始慢慢邊看書邊研究spring的源碼吧!!!
怎樣高效的看源碼
我的答案是帶著具體的問題去看源碼,不然非常容易陷入源碼細(xì)節(jié)中不能自拔,然后就暈了,最后你發(fā)現(xiàn)這看了半天看的是啥玩意啊.
引言
Spring是一個(gè)開源的設(shè)計(jì)層面框架,解決了業(yè)務(wù)邏輯層和其他各層的松耦合問題,將面向接口的編程思想貫穿整個(gè)系統(tǒng)應(yīng)用,同時(shí)它也是Java工作中必備技能之一…
由于記錄的是Spring源碼分析的過程,詳細(xì)用法就不一一贅述了
核心代碼
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency>
用法
public class Application {
public static void main(String[] args) {
BeanDefinitionRegistry beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
ClassPathResource resource = new ClassPathResource("bean.xml");
//整個(gè)資源加載的切入點(diǎn)。
reader.loadBeanDefinitions(resource);
}
}
解密
DefaultListableBeanFactory 是 Spring 注冊(cè)及加載 bean 的默認(rèn)實(shí)現(xiàn),整個(gè)Spring Ioc模板中它可以稱得上始祖。
跟蹤DefaultListableBeanFactory,可以發(fā)現(xiàn)如下代碼塊,該設(shè)計(jì)的目的是什么?
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
舉例來說,當(dāng) A 中有屬性 B 時(shí),那么 Spring 在獲取屬性 A 時(shí),如果發(fā)現(xiàn)屬性 B 未實(shí)例化則會(huì)自動(dòng)實(shí)例化屬性 B,這也是Spring中提供的一個(gè)重要特性,在某些情況下 B 不會(huì)被初始化,比如實(shí)現(xiàn)了 BeanNameAware 接口。
Spring中是這樣介紹的:自動(dòng)裝配時(shí)忽略給定的依賴接口,比如通過其他方式解析Application上下文注冊(cè)依賴,類似于 BeanFactory 通過 BeanFactoryAware 進(jìn)行的注入或者 ApplicationContext 通過 ApplicationContextAware 進(jìn)行的注入。
資源管理
通過 Resource 接口來實(shí)現(xiàn)對(duì) File、URL、Classpath 等資源的管理,Resource 負(fù)責(zé)對(duì)配置文件進(jìn)行讀取,即將配置文件封裝為 Resource,然后交給 XmlBeanDefinitionReader 來處理。

XML 解析
XmlBeanDefinitionReader 是 Spring 資源文件讀取、解析、注冊(cè)的實(shí)現(xiàn),要重點(diǎn)關(guān)注該類。
跟蹤reader.loadBeanDefinitions(resource); ,我們可以見到如下核心代碼(剔除注釋和拋出異常)
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
}
上文代碼首先對(duì) Resource 做了一次編碼操作,目的就是擔(dān)心 XML 存在編碼問題
仔細(xì)觀察InputSource inputSource = new InputSource(inputStream); ,它的包名居然是org.xml.sax,所以我們可以得出Spring采用的是SAX解析,使用 InputSource 來決定如何讀取 XML 文件。
最后將準(zhǔn)備的數(shù)據(jù)通過參數(shù)傳入到真正核心處理部分 doLoadBeanDefinitions(inputSource, encodedResource.getResource())
獲取 Document
1.doLoadBeanDefinitions(inputSource, encodedResource.getResource()); ,省略若干catch和注釋
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
}
2.doLoadDocument(inputSource, resource);
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
首先通過 getValidationModeForResource 獲取 XML 文件的驗(yàn)證模式(DTD 或者 XSD),可以自己設(shè)置驗(yàn)證方式,默認(rèn)是開啟 VALIDATION_AUTO 即自動(dòng)獲取驗(yàn)證模式的,通過 InputStream 讀取 XML 文件,檢查是否包含 DOCTYPE 單詞,包含的話就是 DTD,否則返回 XSD。
常見的 XML 文件驗(yàn)證模式有:
public class XmlValidationModeDetector {
/**
* Indicates that DTD validation should be used (we found a "DOCTYPE" declaration).
*/
public static final int VALIDATION_DTD = 2;
/**
* Indicates that XSD validation should be used (found no "DOCTYPE" declaration).
*/
public static final int VALIDATION_XSD = 3;
public int detectValidationMode(InputStream inputStream) throws IOException {
}
}
在 this.documentLoader.loadDocument 方法中涉及到一個(gè) EntityResolver 參數(shù)
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
}
何為 EntityResolver ? 官方解釋: 如果 SAX 應(yīng)用程序需要實(shí)現(xiàn)自定義處理外部實(shí)體,則必須實(shí)現(xiàn)此接口,并使用 setEntityResolver 方法向SAX 驅(qū)動(dòng)器注冊(cè)一個(gè)實(shí)例。也就是說,對(duì)于解析一個(gè) xml,sax 首先會(huì)讀取該 xml 文檔上的聲明,根據(jù)聲明去尋找相應(yīng)的 DTD 定義,以便對(duì)文檔的進(jìn)行驗(yàn)證,默認(rèn)的尋找規(guī)則,(即:網(wǎng)絡(luò)下載,通過 XML 聲明的 DTD URI地址來下載 DTD的定義),并進(jìn)行認(rèn)證,下載的過程是一個(gè)漫長(zhǎng)的過程,而且當(dāng)網(wǎng)絡(luò)不可用時(shí),這里會(huì)報(bào)錯(cuò),就是因?yàn)橄鄳?yīng)的 dtd 沒找到。
EntityResolver 的作用是項(xiàng)目本身就可以提供一個(gè)如何尋找 DTD 聲明的方法,即由程序來實(shí)現(xiàn)尋找 DTD 的過程,這樣就避免了通過網(wǎng)絡(luò)來尋找相應(yīng)的聲明。

3.EntityResolver 接受兩個(gè)參數(shù):
public abstract InputSource resolveEntity (String publicId,String systemId) throws SAXException, IOException;
3.1 定義bean.xml文件,內(nèi)容如下(XSD模式)
<?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"> </beans>
解析到如下兩個(gè)參數(shù):
- publicId: null
- systemId: http://www.springframework.org/schema/beans/spring-beans.xsd
3.2 定義bean.xml文件,內(nèi)容如下(DTD模式)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> </beans>
解析到如下兩個(gè)參數(shù):
- publicId: -//SPRING//DTD BEAN 2.0//EN
- systemId: http://www.springframework.org/dtd/spring-beans.dtd
3.3 Spring 使用 DelegatingEntityResolver 來解析 EntityResolver
public class DelegatingEntityResolver {
@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable 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;
}
}
我們可以看到針對(duì)不同的模式,采用了不同的解析器
- DTD: 采用 BeansDtdResolver 解析,直接截取 systemId 最后的 *.dtd(如:spring-beans.dtd),然后去當(dāng)前路徑下尋找
- XSD: 采用 PluggableSchemaResolver 解析,默認(rèn)加載 META-INF/Spring.schemas 文件下與 systemId 所對(duì)應(yīng)的 XSD 文件
注冊(cè) Bean
看完解析XML校驗(yàn)后,繼續(xù)跟蹤代碼,看 Spring 是如何根據(jù) Document 注冊(cè) Bean 信息
public class XmlBeanDefinitionReader {
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 創(chuàng)建DocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 記錄統(tǒng)計(jì)前的 BeanDefinition 數(shù)
int countBefore = getRegistry().getBeanDefinitionCount();
// 注冊(cè) BeanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 記錄本次加載 BeanDefinition 的個(gè)數(shù)
return getRegistry().getBeanDefinitionCount() - countBefore;
}
}
注冊(cè) Bean 的時(shí)候首先使用一個(gè) BeanDefinitionParserDelegate 類來判斷是否是默認(rèn)命名空間,實(shí)現(xiàn)是通過判斷 namespace uri 是否和默認(rèn)的 uri 相等:
public class BeanDefinitionParserDelegate {
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
public boolean isDefaultNamespace(@Nullable String namespaceUri) {
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
}
跟蹤 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); ,其中 doc 是通過前面代碼塊中 loadDocument 轉(zhuǎn)換出來的,這個(gè)方法主要目的就是提取出 root 節(jié)點(diǎn)(beans)
public class DefaultBeanDefinitionDocumentReader {
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
}
跟蹤 doRegisterBeanDefinitions(root) ,我們將看到如下處理流程
protected void doRegisterBeanDefinitions(Element root) {
// ...
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
// ...
// 空實(shí)現(xiàn)
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
// 空實(shí)現(xiàn)
postProcessXml(root);
this.delegate = parent;
}
首先對(duì) profile 解析(比較常見的玩法就是不同 profile 初始化的 bean 對(duì)象不同,實(shí)現(xiàn)多環(huán)境)
接下來的解析使用了模板方法模式,其中 preProcessXml 和 postProcessXml 都是空方法,為的就是方便之后的子類在解析前后進(jìn)行一些處理。只需要覆寫這兩個(gè)方法即可。
解析并注冊(cè) BeanDefinition,該部分代碼比較簡(jiǎn)單
public class DefaultBeanDefinitionDocumentReader {
/**
* 解析 root 節(jié)點(diǎn)下的其它節(jié)點(diǎn) import", "alias", "bean".
* @param root節(jié)點(diǎn)名稱
*/
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);
}
}
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);
}
}
/**
* 處理 Bean 標(biāo)簽,然后將其注冊(cè)到注冊(cè)表中去
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
}
委托 BeanDefinitionParserDelegate 類的 parseBeanDefinitionElement 方法進(jìn)行元素解析,返回 BeanDefinitionHolder 類型的實(shí)例 bdHolder(包含了配置文件的各個(gè)屬性class、name、id、alias等)
當(dāng)返回的 bdHolder 不為空的情況下,若默認(rèn)標(biāo)簽的子節(jié)點(diǎn)存在自定義屬性,則再次對(duì)自定義標(biāo)簽進(jìn)行解析
解析完畢后,委托 BeanDefinitionReaderUtils.registerBeanDefinition();對(duì) bdHolder 進(jìn)行注冊(cè)
發(fā)送注冊(cè)事件,告知相關(guān)監(jiān)聽 Bean 已經(jīng)注冊(cè)成功了
總結(jié)
熬過幾個(gè)無人知曉的秋冬春夏,撐過去一切都會(huì)順著你想要的方向走…
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
說點(diǎn)什么
全文代碼:https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter1 (本地下載)
相關(guān)文章
springmvc處理模型數(shù)據(jù)Map過程解析
這篇文章主要介紹了springmvc處理模型數(shù)據(jù)Map過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
Java運(yùn)行時(shí)環(huán)境之ClassLoader類加載機(jī)制詳解
這篇文章主要給大家介紹了關(guān)于Java運(yùn)行時(shí)環(huán)境之ClassLoader類加載機(jī)制的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01
java.util.Collections類—emptyList()方法的使用
這篇文章主要介紹了java.util.Collections類—emptyList()方法的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Elasticsearch查詢Range Query語法示例
這篇文章主要為大家介紹了Elasticsearch查詢Range Query語法示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
MyBatis動(dòng)態(tài)sql查詢及多參數(shù)查詢方式
這篇文章主要介紹了MyBatis動(dòng)態(tài)sql查詢及多參數(shù)查詢方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
Java Character類對(duì)單個(gè)字符操作原理解析
這篇文章主要介紹了Java Character類對(duì)單個(gè)字符操作原理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03

