1. 概述
分析源碼是一件非常具有挑戰(zhàn)性的工作,在正是分析spring的源碼之前我們先來簡(jiǎn)單回顧下spring核心功能的簡(jiǎn)單使用
2. 容器的基本用法
bean是spring最核心的東西,spring就像是一個(gè)大水桶,而bean就是水桶中的水,水桶脫離了水也就沒有什么用處了,我們簡(jiǎn)單看下bean的定義,代碼如下:
public class MyBeanDemo { private String beanName = "bean"; public String getBeanName() { return beanName; } public void setBeanName(String beanName) { this.beanName = beanName; } }
源碼很簡(jiǎn)單,bean沒有特別之處,spring的的目的就是讓我們的bean成為一個(gè)純粹的的POJO,這就是spring追求的,接下來就是在配置文件中定義這個(gè)bean,配置文件如下:
<?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="demo" class="com.yhl.myspring.demo.bean.MyBeanDemo"> <property name="beanName" value="bean demo1"/> </bean> </beans>
在上面的配置中我們可以看到bean的聲明方式,在spring中的bean定義有N中屬性,但是我們只要像上面這樣簡(jiǎn)單的聲明就可以使用了。
具體測(cè)試代碼如下:
public class TestDemo { public static void main(String[] args) { BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-bean.xml")); try { MyBeanDemo bean = (MyBeanDemo)factory.getBean("demo"); System.out.println(bean.getBeanName()); } catch (BeansException e) { e.printStackTrace(); } } }
運(yùn)行上述測(cè)試代碼就可以看到輸出結(jié)果如下圖:

其實(shí)直接使用BeanFactory作為容器對(duì)于Spring的使用并不多見,因?yàn)槠髽I(yè)級(jí)應(yīng)用項(xiàng)目中大多會(huì)使用的是ApplicationContext(后面我們會(huì)講兩者的區(qū)別,這里只是測(cè)試)
3. 功能分析
接下來我們分析2中代碼完成的功能;
- 讀取配置文件spring-bean.xml。
- 根據(jù)spring-beanxml中的配置找到對(duì)應(yīng)的類的配置,并實(shí)例化。
- 調(diào)用實(shí)例化后的實(shí)例
下圖是一個(gè)最簡(jiǎn)單spring功能架構(gòu),如果想完成我們預(yù)想的功能,至少需要3個(gè)類:

其中,
ConfigReader:用于讀取及驗(yàn)證配置文件。我們要用配置文件里面的東西,當(dāng)然首先要做的就是讀取,然后放置在內(nèi)存中。
ReflectionUtil:用于根據(jù)配置文件中的配置進(jìn)行反射實(shí)例化。比如在例2.1中spring-bean.xml出現(xiàn)的
我們就可以根據(jù)bean.demo進(jìn)行實(shí)例化。
APP:用于完成整個(gè)邏輯的串聯(lián)。
4. 工程搭建
在spring的源碼中用于實(shí)現(xiàn)上面功能的是spring-bean這個(gè)工程,所以我們接下來看這個(gè)工程,當(dāng)然spring-core是必須的。
4.1 beans包的層級(jí)結(jié)構(gòu)
閱讀源碼最好的方式是跟著示例操作一遍,我們先看看beans工程的源碼結(jié)構(gòu),如下圖所示:

- src/main/JAVA 用于展現(xiàn)Spring的主要邏輯
- src/main/resources 用于存放系統(tǒng)的配置文件
- src/test/java 用于對(duì)主要邏輯進(jìn)行單元測(cè)試
- src/test/resources 用于存放測(cè)試用的配置文件
4.2 核心類介紹
接下來我們先了解下spring-bean最核心的兩個(gè)類:DefaultListableBeanFactory和XmlBeanDefinitionReader
4.2.1 DefaultListableBeanFactory
XmlBeanFactory繼承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整個(gè)bean加載的核心部分,是Spring注冊(cè)及加載bean的默認(rèn)實(shí)現(xiàn),而對(duì)于XmlBeanFactory與DefaultListableBeanFactory不同的地方其實(shí)是在XmlBeanFactory中使用了自定義的XML讀取器XmlBeanDefinitionReader,實(shí)現(xiàn)了個(gè)性化的BeanDefinitionReader讀取,DefaultListableBeanFactory繼承了AbstractAutowireCapableBeanFactory并實(shí)現(xiàn)了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。以下是ConfigurableListableBeanFactory的層次結(jié)構(gòu)圖以下相關(guān)類圖,

上面類圖中各個(gè)類及接口的作用如下:
- AliasRegistry:定義對(duì)alias的簡(jiǎn)單增刪改等操作
- SimpleAliasRegistry:主要使用map作為alias的緩存,并對(duì)接口AliasRegistry進(jìn)行實(shí)現(xiàn)
- SingletonBeanRegistry:定義對(duì)單例的注冊(cè)及獲取
- BeanFactory:定義獲取bean及bean的各種屬性
- DefaultSingletonBeanRegistry:默認(rèn)對(duì)接口SingletonBeanRegistry各函數(shù)的實(shí)現(xiàn)
- HierarchicalBeanFactory:繼承BeanFactory,也就是在BeanFactory定義的功能的基礎(chǔ)上增加了對(duì)parentFactory的支持
- BeanDefinitionRegistry:定義對(duì)BeanDefinition的各種增刪改操作
- FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基礎(chǔ)上增加了對(duì)FactoryBean的特殊處理功能
- ConfigurableBeanFactory:提供配置Factory的各種方法
- ListableBeanFactory:根據(jù)各種條件獲取bean的配置清單
- AbstractBeanFactory:綜合FactoryBeanRegistrySupport和ConfigurationBeanFactory的功能
- AutowireCapableBeanFactory:提供創(chuàng)建bean、自動(dòng)注入、初始化以及應(yīng)用bean的后處理器
- AbstractAutowireCapableBeanFactory:綜合AbstractBeanFactory并對(duì)接口AutowireCapableBeanFactory進(jìn)行實(shí)現(xiàn)
- ConfigurableListableBeanFactory:BeanFactory配置清單,指定忽略類型及接口等
- DefaultListableBeanFactory:綜合上面所有功能,主要是對(duì)Bean注冊(cè)后的處理
XmlBeanFactory對(duì)DefaultListableBeanFactory類進(jìn)行了擴(kuò)展,主要用于從XML文檔中讀取BeanDefinition,對(duì)于注冊(cè)及獲取Bean都是使用從父類DefaultListableBeanFactory繼承的方法去實(shí)現(xiàn),而唯獨(dú)與父類不同的個(gè)性化實(shí)現(xiàn)就是增加了XmlBeanDefinitionReader類型的reader屬性。在XmlBeanFactory中主要使用reader屬性對(duì)資源文件進(jìn)行讀取和注冊(cè)
4.2.2 XmlBeanDefinitionReader
XML配置文件的讀取是Spring中重要的功能,因?yàn)镾pring的大部分功能都是以配置作為切入點(diǎn)的,可以從XmlBeanDefinitionReader中梳理一下資源文件讀取、解析及注冊(cè)的大致脈絡(luò),先看看各個(gè)類的功能
ResourceLoader:定義資源加載器,主要應(yīng)用于根據(jù)給定的資源文件地址返回對(duì)應(yīng)的Resource
BeanDefinitionReader:主要定義資源文件讀取并轉(zhuǎn)換為BeanDefinition的各個(gè)功能
EnvironmentCapable:定義獲取Environment方法
DocumentLoader:定義從資源文件加載到轉(zhuǎn)換為Document的功能
AbstractBeanDefinitionReader:對(duì)EnvironmentCapable、BeanDefinitionReader類定義功能進(jìn)行實(shí)現(xiàn)
BeanDefinitionDocumentReader:定義讀取Document并注冊(cè)BeanDefinition功能
BeanDefinitionParserDelegate:定義解析Element的各種方法
整個(gè)XML配置文件讀取的大致流程,在XmlBeanDefinitionReader中主要包含以下幾步處理

(1)通過繼承自AbstractBeanDefinitionReader中的方法,來使用ResourceLoader將資源文件路徑轉(zhuǎn)換為對(duì)應(yīng)的Resource文件
(2)通過DocumentLoader對(duì)Resource文件進(jìn)行轉(zhuǎn)換,將Resource文件轉(zhuǎn)換為Document文件
(3)通過實(shí)現(xiàn)接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader類對(duì)Document進(jìn)行解析,并使用BeanDefinitionParserDelegate對(duì)Element進(jìn)行解析
5. 容器的基礎(chǔ)XmlBeanFactory
通過上面的內(nèi)容我們對(duì)spring的容器已經(jīng)有了大致的了解,接下來我們?cè)敿?xì)探索每個(gè)步驟的詳細(xì)實(shí)現(xiàn),接下來要分析的功能都是基于如下代碼:
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-bean.xml"));
通過XmlBeanFactory初始化時(shí)序圖看一看上面代碼的執(zhí)行邏輯,如下圖所示:

時(shí)序圖從TestDemo測(cè)試類開始,首先調(diào)用ClassPathResource的構(gòu)造函數(shù)來構(gòu)造Resource資源文件的實(shí)例對(duì)象,這樣后續(xù)的資源處理就可以用Resource提供的各種服務(wù)來操作了。有了Resource后就可以對(duì)BeanFactory進(jìn)行初始化操作,那配置文件是如何封裝的呢?
5.1 配置文件的封裝
Spring的配置文件讀取是通過ClassPathResource進(jìn)行封裝的,Spring對(duì)其內(nèi)部使用到的資源實(shí)現(xiàn)了自己的抽象結(jié)構(gòu):Resource接口來封裝底層資源,如下源碼:
public interface InputStreamSource { InputStream getInputStream() throws IOException; } public interface Resource extends InputStreamSource { boolean exists(); default boolean isReadable() { return true; } default boolean isOpen() { return false; } default boolean isFile() { return false; } URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; String getFilename(); String getDescription(); }
通過源碼我們了解到InputStreamSource封裝任何能返回InputStream的類,比如File、Classpath下的資源和Byte Array等, 它只有一個(gè)方法定義:getInputStream(),該方法返回一個(gè)新的InputStream對(duì)象
Resource接口抽象了所有Spring內(nèi)部使用到的底層資源:File、URL、Classpath等。首先,它定義了3個(gè)判斷當(dāng)前資源狀態(tài)的方法:存在性(exists)、可讀性(isReadable)、是否處于打開狀態(tài)(isOpen)。另外,Resource接口還提供了不同資源到URL、URI、File類型的轉(zhuǎn)換,以及獲取lastModified屬性、文件名(不帶路徑信息的文件名,getFilename())的方法,為了便于操作,Resource還提供了基于當(dāng)前資源創(chuàng)建一個(gè)相對(duì)資源的方法:createRelative(),還提供了getDescription()方法用于在錯(cuò)誤處理中的打印信息。
對(duì)不同來源的資源文件都有相應(yīng)的Resource實(shí)現(xiàn):文件(FileSystemResource)、Classpath資源(ClassPathResource)、URL資源(UrlResource)、InputStream資源(InputStreamResource)、Byte數(shù)組(ByteArrayResource)等,相關(guān)類圖如下所示:

在日常開發(fā)中我們可以直接使用spring提供的類來加載資源文件,比如在希望加載資源文件時(shí)可以使用下面的代碼:
Resource resource = new ClassPathResource("spring-bean.xml"); InputStream is = resource.getInputStream();
當(dāng)通過Resource相關(guān)類完成了對(duì)配置文件進(jìn)行封裝后,配置文件的讀取工作就全權(quán)交給XmlBeanDefinitionReader來處理了。
接下來就進(jìn)入到XmlBeanFactory的初始化過程了,XmlBeanFactory的初始化有若干辦法,Spring提供了很多的構(gòu)造函數(shù),在這里分析的是使用Resource實(shí)例作為構(gòu)造函數(shù)參數(shù)的辦法,代碼如下:
public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }
上面函數(shù)中的代碼this.reader.loadBeanDefinitions(resource)才是資源加載的真正實(shí)現(xiàn),時(shí)序圖中提到的XmlBeanDefinitionReader加載數(shù)據(jù)就是在這里完成的,但是在XmlBeanDefinitionReader加載數(shù)據(jù)前還有一個(gè)調(diào)用父類構(gòu)造函數(shù)初始化的過程:super(parentBeanFactory),我們按照代碼層級(jí)進(jìn)行跟蹤,首先跟蹤到如下父類代碼:
public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) { super(parentBeanFactory); }
然后繼續(xù)跟蹤,跟蹤代碼到父類AbstractAutowireCapableBeanFactory的構(gòu)造函數(shù)中:
public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) { this(); setParentBeanFactory(parentBeanFactory); } public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
這里要提及一下ignoreDependencyInterface方法,此方法的主要功能是忽略給定接口的自動(dòng)裝配功能,目的是:實(shí)現(xiàn)了BeanNameAware接口的屬性,不會(huì)被Spring自動(dòng)初始化。自動(dòng)裝配時(shí)忽略給定的依賴接口,典型應(yīng)用是通過其他方式解析Application上下文注冊(cè)依賴,類似于BeanFactory通過BeanFactoryAware進(jìn)行注入或者ApplicationContext通過ApplicationContextAware進(jìn)行注入。
5.2 bean加載
在之前XmlBeanFactory構(gòu)造函數(shù)中調(diào)用了XmlBeanDefinitionReader類型的reader屬性提供的方法this.reader.loadBeanDefinitions(resource),而這句代碼則是整個(gè)資源加載的切入點(diǎn),這個(gè)方法的時(shí)序圖如下:

我們來梳理下上述時(shí)序圖的處理過程:
(1)封裝資源文件。當(dāng)進(jìn)入XmlBeanDefinitionReader后首先對(duì)參數(shù)Resource使用EncodedResource類進(jìn)行封裝
(2)獲取輸入流。從Resource中獲取對(duì)應(yīng)的InputStream并構(gòu)造InputSource
(3)通過構(gòu)造的InputSource實(shí)例和Resource實(shí)例繼續(xù)調(diào)用函數(shù)doLoadBeanDefinitions,loadBeanDefinitions函數(shù)具體的實(shí)現(xiàn)過程:

EncodedResource的作用是對(duì)資源文件的編碼進(jìn)行處理的,其中的主要邏輯體現(xiàn)在getReader()方法中,當(dāng)設(shè)置了編碼屬性的時(shí)候Spring會(huì)使用相應(yīng)的編碼作為輸入流的編碼,在構(gòu)造好了encodeResource對(duì)象后,再次轉(zhuǎn)入了可復(fù)用方法loadBeanDefinitions(new EncodedResource(resource)),這個(gè)方法內(nèi)部才是真正的數(shù)據(jù)準(zhǔn)備階段,代碼如下:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
在上面冗長(zhǎng)的代碼中假如不考慮異常類代碼,其實(shí)只做了三件事
- 獲取對(duì)XML文件的驗(yàn)證模式
- 加載XML文件,并得到對(duì)應(yīng)的Document
- 根據(jù)返回的Document注冊(cè)Bean信息
5.3 獲取XML的驗(yàn)證模式
XML文件的驗(yàn)證模式保證了XML文件的正確性,而比較常用的驗(yàn)證模式有兩種:DTD和XSD
5.3.1 DTD和XSD區(qū)別
DTD(Document Type Definition)即文檔類型定義,是一種XML約束模式語言,是XML文件的驗(yàn)證機(jī)制,屬于XML文件組成的一部分。DTD是一種保證XML文檔格式正確的有效方法,可以通過比較XML文檔和DTD文件來看文檔是否符合規(guī)范,元素和標(biāo)簽使用是否正確。一個(gè)DTD文檔包含:元素的定義規(guī)則,元素間關(guān)系的定義規(guī)則,元素可使用的屬性,可使用的實(shí)體或符合規(guī)則。
使用DTD驗(yàn)證模式的時(shí)候需要在XML文件的頭部聲明,以下是在Spring中使用DTD聲明方式的代碼:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">
而以Spring為例,具體的Spring-beans-2.0.dtd部分如下:
<!ELEMENT beans ( description?, (import | alias | bean)* )> <!-- Default values for all bean definitions. Can be overridden at the "bean" level. See those attribute definitions for details. --> <!ATTLIST beans default-lazy-init (true | false) "false"> <!ATTLIST beans default-autowire (no | byName | byType | constructor | autodetect) "no"> <!ATTLIST beans default-dependency-check (none | objects | simple | all) "none"> <!ATTLIST beans default-init-method CDATA #IMPLIED> <!ATTLIST beans default-destroy-method CDATA #IMPLIED> <!ATTLIST beans default-merge (true | false) "false"> <!-- Element containing informative text describing the purpose of the enclosing element. Always optional. Used primarily for user documentation of XML bean definition documents. --> <!ELEMENT description (#PCDATA)> <!-- Specifies an XML bean definition resource to import. --> <!ELEMENT import EMPTY>
XML Schema語言就是XSD(XML Schemas Definition)。XML Schema描述了XML文檔的結(jié)構(gòu),可以用一個(gè)指定的XML Schema來驗(yàn)證某個(gè)XML文檔,以檢查該XML文檔是否符合其要求,文檔設(shè)計(jì)者可以通過XML Schema指定一個(gè)XML文檔所允許的結(jié)構(gòu)和內(nèi)容,并可據(jù)此檢查一個(gè)XML文檔是否是有效的。
在使用XML Schema文檔對(duì)XML實(shí)例文檔進(jìn)行檢驗(yàn),除了要聲明名稱空間外(xmlns=http://www.Springframework.org/schema/beans),還必須指定該名稱空間所對(duì)應(yīng)的XML Schema文檔的存儲(chǔ)位置,通過schemaLocation屬性來指定名稱空間所對(duì)應(yīng)的XML Schema文檔的存儲(chǔ)位置,它包含兩個(gè)部分,一部分是名稱空間的URI,另一部分就該名稱空間所標(biāo)識(shí)的XML Schema文件位置或URL地址(xsi:schemaLocation=”http://www.Springframework.org/schema/beanshttp://www.Springframework.org/schema/beans/Spring-beans.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"> <bean id="demo" class="com.yhl.myspring.demo.bean.MyBeanDemo"> <property name="beanName" value="bean demo1"/> </bean> </beans>
Spring-beans-4.3.xsd部分代碼如下:

5.3.2 驗(yàn)證模式的讀取
在spring中,是通過getValidationModeForResource方法來獲取對(duì)應(yīng)資源的驗(yàn)證模式,其源碼如下:
protected int getValidationModeForResource(Resource resource) { int validationModeToUse = getValidationMode(); if (validationModeToUse != VALIDATION_AUTO) { return validationModeToUse; } int detectedMode = detectValidationMode(resource); if (detectedMode != VALIDATION_AUTO) { return detectedMode; } // Hmm, we didn't get a clear indication... Let's assume XSD, // since apparently no DTD declaration has been found up until // detection stopped (before finding the document's root tag). return VALIDATION_XSD; }
方法的實(shí)現(xiàn)還是很簡(jiǎn)單的,如果設(shè)定了驗(yàn)證模式則使用設(shè)定的驗(yàn)證模式(可以通過使用XmlBeanDefinitionReader中的setValidationMode方法進(jìn)行設(shè)定),否則使用自動(dòng)檢測(cè)的方式。而自動(dòng)檢測(cè)驗(yàn)證模式的功能是在函數(shù)detectValidationMode方法中,而在此方法中又將自動(dòng)檢測(cè)驗(yàn)證模式的工作委托給了專門處理類XmlValidationModeDetector的validationModeDetector方法,具體代碼如下:
public int detectValidationMode(InputStream inputStream) throws IOException { // Peek into the file to look for DOCTYPE. BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); try { boolean isDtdValidated = false; String content; while ((content = reader.readLine()) != null) { content = consumeCommentTokens(content); if (this.inComment || !StringUtils.hasText(content)) { continue; } if (hasDoctype(content)) { isDtdValidated = true; break; } if (hasOpeningTag(content)) { // End of meaningful data... break; } } return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD); } catch (CharConversionException ex) { // Choked on some character encoding... // Leave the decision up to the caller. return VALIDATION_AUTO; } finally { reader.close(); } }
Spring用來檢測(cè)驗(yàn)證模式的辦法就是判斷是否包含DOCTYPE,如果包含就是DTD,否則就是XSD
5.4. 獲取Document
經(jīng)過了驗(yàn)證模式準(zhǔn)備的步驟就可以進(jìn)行Document加載了,對(duì)于文檔的讀取委托給了DocumentLoader去執(zhí)行,這里的DocumentLoader是個(gè)接口,而真正調(diào)用的是DefaultDocumentLoader,解析代碼如下:
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }
分析代碼,首選創(chuàng)建DocumentBuildFactory,再通過DocumentBuilderFactory創(chuàng)建DocumentBuilder,進(jìn)而解析InputSource來返回Document對(duì)象。對(duì)于參數(shù)entityResolver,傳入的是通過getEntityResolver()函數(shù)獲取的返回值,代碼如下:
protected EntityResolver getEntityResolver() { if (this.entityResolver == null) { // Determine default EntityResolver to use. ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader != null) { this.entityResolver = new ResourceEntityResolver(resourceLoader); } else { this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); } } return this.entityResolver; }
這個(gè)entityResolver是做什么用的呢,接下來我們?cè)敿?xì)分析下。
5.4.1 EntityResolver 的用法
如果SAX應(yīng)用程序需要實(shí)現(xiàn)自定義處理外部實(shí)體,則必須實(shí)現(xiàn)此接口并使用setEntityResolver方法向SAX驅(qū)動(dòng)器注冊(cè)一個(gè)實(shí)例。也就是說,對(duì)于解析一個(gè)XML,SAX首先讀取該XML文檔上的聲明,根據(jù)聲明去尋找相應(yīng)的DTD定義,以便對(duì)文檔進(jìn)行一個(gè)驗(yàn)證,默認(rèn)的尋找規(guī)則,即通過網(wǎng)絡(luò)(實(shí)現(xiàn)上就是聲明DTD的URI地址)來下載相應(yīng)的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聲明的過程,比如將DTD文件放到項(xiàng)目中某處,在實(shí)現(xiàn)時(shí)直接將此文檔讀取并返回給SAX即可,在EntityResolver的接口只有一個(gè)方法聲明:
public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
它接收兩個(gè)參數(shù)publicId和systemId,并返回一個(gè)InputSource對(duì)象,以特定配置文件來進(jìn)行講解
(1)如果在解析驗(yàn)證模式為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>
則會(huì)讀取到以下兩個(gè)參數(shù)
- publicId:null
- systemId:http://www.Springframework.org/schema/beans/Spring-beans.xsd
(2)如果解析驗(yàn)證模式為DTD的配置文件,代碼如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd"> .... </beans>
讀取到以下兩個(gè)參數(shù)
- publicId:-//Spring//DTD BEAN 2.0//EN
- systemId:http://www.Springframework.org/dtd/Spring-beans-2.0.dtd
一般都會(huì)把驗(yàn)證文件放置在自己的工程里,如果把URL轉(zhuǎn)換為自己工程里對(duì)應(yīng)的地址文件呢?以加載DTD文件為例來看看Spring是如何實(shí)現(xiàn)的。根據(jù)之前Spring中通過getEntityResolver()方法對(duì)EntityResolver的獲取,我們知道,Spring中使用DelegatingEntityResolver類為EntityResolver的實(shí)現(xiàn)類,resolveEntity實(shí)現(xiàn)方法如下:
@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ì)不同的驗(yàn)證模式,Spring使用了不同的解析器解析,比如加載DTD類型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去當(dāng)前路徑下尋找,而加載XSD類型的PluggableSchemaResolver類的resolveEntity是默認(rèn)到META-INF/Spring.schemas文件中找到systemId所對(duì)應(yīng)的XSD文件并加載,下面是BeansDtdResolver的源碼:
@Override @Nullable public InputSource resolveEntity(String publicId, @Nullable String systemId) throws IOException { if (logger.isTraceEnabled()) { logger.trace("Trying to resolve XML entity with public ID [" + publicId + "] and system ID [" + systemId + "]"); } if (systemId != null && systemId.endsWith(DTD_EXTENSION)) { int lastPathSeparator = systemId.lastIndexOf('/'); int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator); if (dtdNameStart != -1) { String dtdFile = DTD_NAME + DTD_EXTENSION; if (logger.isTraceEnabled()) { logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath"); } try { Resource resource = new ClassPathResource(dtdFile, getClass()); InputSource source = new InputSource(resource.getInputStream()); source.setPublicId(publicId); source.setSystemId(systemId); if (logger.isDebugEnabled()) { logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile); } return source; } catch (IOException ex) { if (logger.isDebugEnabled()) { logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex); } } } } // Use the default behavior -> download from website or wherever. return null; }
5.5 解析及注冊(cè)BeanDefinitions
當(dāng)把文件轉(zhuǎn)換成Document后,接下來就是對(duì)bean的提取及注冊(cè),當(dāng)程序已經(jīng)擁有了XML文檔文件的Document實(shí)例對(duì)象時(shí),就會(huì)被引入到XmlBeanDefinitionReader.registerBeanDefinitions這個(gè)方法:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
其中的doc參數(shù)即為上節(jié)讀取的document,而BeanDefinitionDocumentReader是一個(gè)接口,而實(shí)例化的工作是在createBeanDefinitionDocumentReader()中完成的,而通過此方法,BeanDefinitionDocumentReader真正的類型其實(shí)已經(jīng)是DefaultBeanDefinitionDocumentReader了,進(jìn)入DefaultBeanDefinitionDocumentReader后,發(fā)現(xiàn)這個(gè)方法的重要目的之一就是提取root,以便于再次將root作為參數(shù)繼續(xù)BeanDefinition的注冊(cè),如下代碼:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
通過這里我們看到終于到了解析邏輯的核心方法doRegisterBeanDefinitions,接著跟蹤源碼如下:
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isInfoEnabled()) { logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }12345678910111213141516171819202122
我們看到首先要解析profile屬性,然后才開始XML的讀取,具體的代碼如下:
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); } }
在Spring的XML配置里面有兩大類Bean聲明,一個(gè)是默認(rèn)的,如:
另一類就是自定義的,如:
而這兩種方式的讀取及解析差別是非常大的,如果采用Spring默認(rèn)的配置,Spring當(dāng)然知道該怎么做,但如果是自定義的,那么就需要用戶實(shí)現(xiàn)一些接口及配置了。對(duì)于根節(jié)點(diǎn)或子節(jié)點(diǎn)如果是默認(rèn)命名空間的話采用parseDefaultElement方法進(jìn)行解析,否則使用delegate.parseCustomElement方法對(duì)自定義命名空間進(jìn)行解析。而判斷是否默認(rèn)命名空間還是自定義命名空間的辦法其實(shí)是使用node.getNamespaceURI()獲取命名空間,并與Spring中固定的命名空間http://www.springframework.org/schema/beans進(jìn)行對(duì)比,如果一致則認(rèn)為是默認(rèn),否則就認(rèn)為是自定義。
profile的用法
通過profile標(biāo)記不同的環(huán)境,可以通過設(shè)置spring.profiles.active和spring.profiles.default激活指定profile環(huán)境。如果設(shè)置了active,default便失去了作用。如果兩個(gè)都沒有設(shè)置,那么帶有profiles的bean都不會(huì)生成。
有多種方式來設(shè)置這兩個(gè)屬性:
作為DispatcherServlet的初始化參數(shù); 作為web應(yīng)用的上下文參數(shù); 作為JNDI條目; 作為環(huán)境變量; System.set("spring.profiles.active","prod") 作為JVM的系統(tǒng)屬性; -Dspring.profiles.active="prod" 在集成測(cè)試類上,使用@ActiveProfiles注解配置。
以前兩種方式舉例,它們都可以在web.xml中設(shè)置:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name></display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <context-param> <param-name>applicationContext</param-name> <param-value>/applicationContext.xml</param-value> </context-param> <!-- 在上下文中設(shè)置profile的默認(rèn)值 --> <context-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 在servlet中設(shè)置profile的默認(rèn)值 --> <init-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>