摘要:解密是注冊及加載的默認實現,整個模板中它可以稱得上始祖。中是這樣介紹的自動裝配時忽略給定的依賴接口,比如通過其他方式解析上下文注冊依賴,類似于通過進行的注入或者通過進行的注入。解析是資源文件讀取解析注冊的實現,要重點關注該類。
Spring是一個開源的設計層面框架,解決了業務邏輯層和其他各層的松耦合問題,將面向接口的編程思想貫穿整個系統應用,同時它也是Java工作中必備技能之一...前言
由于記錄的是Spring源碼分析的過程,詳細用法就不一一贅述了
核心代碼
用法org.springframework spring-context 5.0.2.RELEASE
public class Application { public static void main(String[] args) { BeanDefinitionRegistry beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); ClassPathResource resource = new ClassPathResource("bean.xml"); //整個資源加載的切入點。 reader.loadBeanDefinitions(resource); } }解密
DefaultListableBeanFactory 是 Spring 注冊及加載 bean 的默認實現,整個Spring Ioc模板中它可以稱得上始祖。
跟蹤DefaultListableBeanFactory,可以發現如下代碼塊,該設計的目的是什么?
public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
舉例來說,當 A 中有屬性 B 時,那么 Spring 在獲取屬性 A 時,如果發現屬性 B 未實例化則會自動實例化屬性 B,這也是Spring中提供的一個重要特性,在某些情況下 B 不會被初始化,比如實現了 BeanNameAware 接口。
Spring中是這樣介紹的:自動裝配時忽略給定的依賴接口,比如通過其他方式解析Application上下文注冊依賴,類似于 BeanFactory 通過 BeanFactoryAware 進行的注入或者 ApplicationContext 通過 ApplicationContextAware 進行的注入。
資源管理通過 Resource 接口來實現對 File、URL、Classpath 等資源的管理,Resource 負責對配置文件進行讀取,即將配置文件封裝為 Resource,然后交給 XmlBeanDefinitionReader 來處理。
XML 解析XmlBeanDefinitionReader 是 Spring 資源文件讀取、解析、注冊的實現,要重點關注該類。
跟蹤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(); } } }
上文代碼首先對 Resource 做了一次編碼操作,目的就是擔心 XML 存在編碼問題
仔細觀察InputSource inputSource = new InputSource(inputStream);,它的包名居然是org.xml.sax,所以我們可以得出Spring采用的是SAX解析,使用 InputSource 來決定如何讀取 XML 文件。
最后將準備的數據通過參數傳入到真正核心處理部分 doLoadBeanDefinitions(inputSource, encodedResource.getResource())
獲取 Document1.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 文件的驗證模式(DTD 或者 XSD),可以自己設置驗證方式,默認是開啟 VALIDATION_AUTO 即自動獲取驗證模式的,通過 InputStream 讀取 XML 文件,檢查是否包含 DOCTYPE 單詞,包含的話就是 DTD,否則返回 XSD。
常見的 XML 文件驗證模式有:
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 方法中涉及到一個 EntityResolver 參數
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { }
何為 EntityResolver ? 官方解釋: 如果 SAX 應用程序需要實現自定義處理外部實體,則必須實現此接口,并使用 setEntityResolver 方法向SAX 驅動器注冊一個實例。也就是說,對于解析一個 xml,sax 首先會讀取該 xml 文檔上的聲明,根據聲明去尋找相應的 DTD 定義,以便對文檔的進行驗證,默認的尋找規則,(即:網絡下載,通過 XML 聲明的 DTD URI地址來下載 DTD的定義),并進行認證,下載的過程是一個漫長的過程,而且當網絡不可用時,這里會報錯,就是因為相應的 dtd 沒找到。
EntityResolver 的作用是項目本身就可以提供一個如何尋找 DTD 聲明的方法,即由程序來實現尋找 DTD 的過程,這樣就避免了通過網絡來尋找相應的聲明。
3.EntityResolver 接受兩個參數:
public abstract InputSource resolveEntity (String publicId,String systemId) throws SAXException, IOException;
3.1 定義bean.xml文件,內容如下(XSD模式)
解析到如下兩個參數:
publicId: null
systemId: http://www.springframework.or...
3.2 定義bean.xml文件,內容如下(DTD模式)
解析到如下兩個參數:
publicId: -//SPRING//DTD BEAN 2.0//EN
systemId: http://www.springframework.or...
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; } }
我們可以看到針對不同的模式,采用了不同的解析器
DTD: 采用 BeansDtdResolver 解析,直接截取 systemId 最后的 *.dtd(如:spring-beans.dtd),然后去當前路徑下尋找
XSD: 采用 PluggableSchemaResolver 解析,默認加載 META-INF/Spring.schemas 文件下與 systemId 所對應的 XSD 文件
注冊 Bean看完解析XML校驗后,繼續跟蹤代碼,看 Spring 是如何根據 Document 注冊 Bean 信息
public class XmlBeanDefinitionReader { public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 創建DocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 記錄統計前的 BeanDefinition 數 int countBefore = getRegistry().getBeanDefinitionCount(); // 注冊 BeanDefinition documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 記錄本次加載 BeanDefinition 的個數 return getRegistry().getBeanDefinitionCount() - countBefore; } }
注冊 Bean 的時候首先使用一個 BeanDefinitionParserDelegate 類來判斷是否是默認命名空間,實現是通過判斷 namespace uri 是否和默認的 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 轉換出來的,這個方法主要目的就是提取出 root 節點(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); // ... // 空實現 preProcessXml(root); parseBeanDefinitions(root, this.delegate); // 空實現 postProcessXml(root); this.delegate = parent; }
首先對 profile 解析(比較常見的玩法就是不同 profile 初始化的 bean 對象不同,實現多環境)
接下來的解析使用了模板方法模式,其中 preProcessXml 和 postProcessXml 都是空方法,為的就是方便之后的子類在解析前后進行一些處理。只需要覆寫這兩個方法即可。
解析并注冊 BeanDefinition,該部分代碼比較簡單
public class DefaultBeanDefinitionDocumentReader { /** * 解析 root 節點下的其它節點 import", "alias", "bean". * @param root節點名稱 */ 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 標簽,然后將其注冊到注冊表中去 */ 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 方法進行元素解析,返回 BeanDefinitionHolder 類型的實例 bdHolder(包含了配置文件的各個屬性class、name、id、alias等)
當返回的 bdHolder 不為空的情況下,若默認標簽的子節點存在自定義屬性,則再次對自定義標簽進行解析
解析完畢后,委托 BeanDefinitionReaderUtils.registerBeanDefinition();對 bdHolder 進行注冊
發送注冊事件,告知相關監聽 Bean 已經注冊成功了
總結熬過幾個無人知曉的秋冬春夏,撐過去一切都會順著你想要的方向走...
說點什么全文代碼:https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter1
個人QQ:1837307557
battcn開源群(適合新手):391619659
微信公眾號:battcn(歡迎調戲)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68190.html
Spring是一個開源的設計層面框架,解決了業務邏輯層和其他各層的松耦合問題,將面向接口的編程思想貫穿整個系統應用,同時它也是Java工作中必備技能之一... 前言 緊跟上篇 Spring解密 - XML解析 與 Bean注冊 ,我們接著往下分析源碼 解密 在 Spring 的 XML 配置里面有兩大類聲明,一個是默認的如 ,另一類就是自定義的如,兩種標簽的解析方式差異是非常大的。parseBe...
摘要:自定義標簽在講解自定義標簽解析之前,先看下如何自定義標簽定義文件定義一個文件描述組件內容聲明命名空間值得注意的是與可以是不存在,只要映射到指定就行了。 Spring是一個開源的設計層面框架,解決了業務邏輯層和其他各層的松耦合問題,將面向接口的編程思想貫穿整個系統應用,同時它也是Java工作中必備技能之一... 前言 在 上一節 Spring解密 - 默認標簽的解析 中,重點分析了...
摘要:判斷調用哪個構造方法的過程會采用緩存機制,如果已經解析過則不需要重復解析而是從中的屬性緩存的值去取,否則需再次解析。 Spring是一個開源的設計層面框架,解決了業務邏輯層和其他各層的松耦合問題,將面向接口的編程思想貫穿整個系統應用,同時它也是Java工作中必備技能之一... 前言 在 Spring解密 - XML解析 與 Bean注冊 中,講了 Bean的解析,本章將詳細講解Sp...
摘要:前言以下源碼基于版本解析。實現源碼分析對于的實現,總結來說就是定位加載和注冊。定位就是需要定位配置文件的位置,加載就是將配置文件加載進內存注冊就是通過解析配置文件注冊。下面我們從其中的一種使用的方式一步一步的分析的實現源碼。 前言 以下源碼基于Spring 5.0.2版本解析。 什么是IOC容器? 容器,顧名思義可以用來容納一切事物。我們平常所說的Spring IOC容器就是一個可以容...
摘要:使用別名時,容器首先將別名元素所定義的別名注冊到容器中。調用的方法向容器注冊解析的通過對對象的解析和封裝返回一個通過這個來注冊對象當調用向容器注冊解析的時,真正完成注冊功能的是。 文章參考來自:https://www.cnblogs.com/ITtan... 文章代碼來自 spring-boot 1.4.1 Release版本 Spring IoC容器對Bean定義資源的載入是從ref...
閱讀 553·2023-04-26 02:59
閱讀 691·2023-04-25 16:02
閱讀 2154·2021-08-05 09:55
閱讀 3544·2019-08-30 15:55
閱讀 4640·2019-08-30 15:44
閱讀 1797·2019-08-30 13:02
閱讀 2193·2019-08-29 16:57
閱讀 2288·2019-08-26 13:35