《Spring原始碼深度解析》學習筆記——Spring的整體架構與容器的基本實現
Spring的整體架構
Spring框架是一個分層架構,它包含一系列的功能要素,並被分為大約20個模組,如下圖所示
這些模組被總結為以下幾個部分:
-
Core Container
Core Container(核心容器)包含有Core、Beans、Context和Expression Language模組
Core和Beans模組是框架的基礎部分,提供IoC(轉控制)和依賴注入特性。這裡的基礎概念是BeanFactory,它提供對Factory模式的經典實現來消除對程式性單例模式的需要,並真正地允許你從程式邏輯中分離出依賴關係和配置- Core模組主要包含Spring框架基本的核心工具類
- Beans模組是所有應用都要用到的,它包含訪問配置檔案、建立和管理bean以及進行Inversion of Control/Dependency Injection(Ioc/DI)操作相關的所有類
- Context模組構建於Core和Beans模組基礎之上,提供了一種類似於JNDI註冊器的框架式的物件訪問方法。Context模組繼承了Beans的特性,為Spring核心提供了大量擴充套件,添加了對國際化(如資源繫結)、事件傳播、資源載入和對Context的透明建立的支援。ApplicationContext介面是Context模組的關鍵
- Expression Language模組提供了一個強大的表示式語言用於在執行時查詢和操縱物件,該語言支援設定/獲取屬性的值,屬性的分配,方法的呼叫,訪問陣列上下文、容器和索引器、邏輯和算術運算子、命名變數以及從Spring的IoC容器中根據名稱檢索物件
-
Data Access/Integration
- JDBC模組提供了一個JDBC抽象層,它可以消除冗長的JDBC編碼和解析資料庫廠商特有的錯誤程式碼,這個模組包含了Spring對JDBC資料訪問進行封裝的所有類
- ORM模組為流行的物件-關係對映API,如JPA、JDO、Hibernate、iBatis等,提供了一個互動層,利用ORM封裝包,可以混合使用所有Spring提供的特性進行O/R對映,如前邊提到的簡單宣告性事務管理
-
Web
Web上下文模組建立在應用程式上下文模組之上,為基於Web的應用程式提供了上下文,所以Spring框架支援與Jakarta Struts的整合。Web模組還簡化了處理多部分請求以及將請求引數繫結到域物件的工作。Web層包含了Web、Web-Servlet、Web-Struts和Web、Porlet模組
- Web模組:提供了基礎的面向Web的整合特性,例如,多檔案上傳、使用Servlet listeners初始化IoC容器以及一個面向Web的應用上下文,它還包含了Spring遠端支援中Web的相關部分
- Web-Servlet模組web.servlet.jar:該模組包含Spring的model-view-controller(MVC)實現,Spring的MVC框架使得模型範圍內的程式碼和web forms之間能夠清楚地分離開來,並與Spring框架的其他特性基礎在一起
- Web-Struts模組:該模組提供了對Struts的支援,使得類在Spring應用中能夠與一個典型的Struts Web層整合在一起
- Web-Porlet模組:提供了用於Portlet環境和Web-Servlet模組的MVC的實現
-
AOP
AOP模組提供了一個符合AOP聯盟標準的面向切面程式設計的實現,它讓你可以定義例如方法攔截器和切點,從而將邏輯程式碼分開,降低它們之間的耦合性,利用source-level的元資料功能,還可以將各種行為資訊合併到你的程式碼中
Spring AOP模組為基於Spring的應用程式中的物件提供了事務管理服務,通過使用Spring AOP,不用依賴EJB元件,就可以將宣告性事務管理整合到應用程式中
-
Test
Test模組支援使用Junit和TestNG對Spring元件進行測試
容器的基本實現
Spring的結構組成
beans包的層級結構
beans包中的各個原始碼包的功能如下
- src/main/java 用於展現Spring的主要邏輯
- src/main/resources 用於存放系統的配置檔案
- src/test/java 用於對主要邏輯進行單元測試
- src/test/resources 用於存放測試用的配置檔案
核心類介紹
1.DefaultListableBeanFactory
XmlBeanFactory繼承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整個bean載入的核心部分,是Spring註冊及載入bean的預設實現,而對於XmlBeanFactory與DefaultListableBeanFactory不同的地方其實是在XmlBeanFactory中使用了自定義的XML讀取器XmlBeanDefinitionReader,實現了個性化的BeanDefinitionReader讀取,DefaultListableBeanFactory繼承了AbstractAutowireCapableBeanFactory並實現了ConfigURableListableBeanFactory以及BeanDefinitionRegistry介面。以下是ConfigURationListableBeanFactory的層次結構圖以下相關類圖
容器載入相關類圖
類圖中各個類的作用:
- AliasRegistry:定義對alias的簡單增刪改等操作
- SimpleAliasRegistry:主要使用map作為alias的快取,並對介面AliasRegistry進行實現
- SingletonBeanRegistry:定義對單例的註冊及獲取
- BeanFactory:定義獲取bean及bean的各種屬性
- DefaultSingletonBeanRegistry:對介面SingletonBeanRegistry各函式的實現
- HierarchicalBeanFactory:繼承BeanFactory,也就是在BeanFactory定義的功能的基礎上增加了對parentFactory的支援
- BeanDefinitionRegistry:定義對BeanDefinition的各種增刪改操作
- FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基礎上增加了對FactoryBean的特殊處理功能
- ConfigurableBeanFactory:提供配置Factory的各種方法
- ListableBeanFactory:根據各種條件獲取bean的配置清單
- AbstractBeanFactory:綜合FactoryBeanRegistrySupport和ConfigurationBeanFactory的功能
- AutowireCapableBeanFactory:提供建立bean、自動注入、初始化以及應用bean的後處理器
- AbstractAutowireCapableBeanFactory:綜合AbstractBeanFactory並對介面AutowireCapableBeanFactory進行實現
- ConfigurableListableBeanFactory:BeanFactory配置清單,指定忽略型別及介面等
- DefaultListableBeanFactory:綜合上面所有功能,主要是對Bean註冊後的處理
XmlBeanFactory對DefaultListableBeanFactory類進行了擴充套件,主要用於從XML文件中讀取BeanDefinition,對於註冊及獲取Bean都是使用從父類DefaultListableBeanFactory繼承的方法去實現,而唯獨與父類不同的個性化實現就是增加了XmlBeanDefinitionReader型別的reader屬性。在XmlBeanFactory中主要使用reader屬性對資原始檔進行讀取和註冊
2.XmlBeanDefinitionReader
XML配置檔案的讀取是Spring中重要的功能,因為Spring的大部分功能都是以配置作為切入點的,可以從XmlBeanDefinitionReader中梳理一下資原始檔讀取、解析及註冊的大致脈絡,首先看看各個類的功能
- ResourceLoader:定義資源載入器,主要應用於根據給定的資原始檔地址返回對應的Resource
- BeanDefinitionReader:主要定義資原始檔讀取並轉換為BeanDefinition的各個功能
- EnvironmentCapable:定義獲取Environment方法
- DocumentLoader:定義從資原始檔載入到轉換為Document的功能
- AbstractBeanDefinitionReader:對EnvironmentCapable、BeanDefinitionReader類定義的功能進行實現
- BeanDefinitionDocumentReader:定義讀取Document並註冊BeanDefinition功能
- BeanDefinitionParserDelegate:定義解析Element的各種方法
整個XML配置檔案讀取的大致流程,在XmlBeanDefinitionReader中主要包含以下幾步處理
(1)通過繼承自AbstractBeanDefinitionReader中的方法,來使用ResourceLoader將資原始檔路徑轉換為對應的Resource檔案
(2)通過DocumentLoader對Resource檔案進行轉換,將Resource檔案轉換為Document檔案
(3)通過實現介面BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader類對Document進行解析,並使用BeanDefinitionParserDelegate對Element進行解析
容器的基礎XmlBeanFactory
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
- 1
通過XmlBeanFactory初始化時序圖看一看上面程式碼的執行邏輯
時序圖從BeanFactoryTest測試類開始,首先呼叫ClassPathResource的建構函式來構造Resource資原始檔的例項物件,這樣後續的資源處理就可以用Resource提供的各種服務來操作了
配置檔案封裝
Spring的配置檔案讀取是通過ClassPathResource進行封裝的,Spring對其內部使用到的資源實現了自己的抽象結構:Resource介面來封裝底層資源
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String var1) throws IOException;
String getFilename();
String getDescription();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
InputStreamSource封裝任何能返回InputStream的類,比如File、Classpath下的資源和Byte Array等, 它只有一個方法定義:getInputStream(),該方法返回一個新的InputStream物件
Resource介面抽象了所有Spring內部使用到的底層資源:File、URL、Classpath等。首先,它定義了3個判斷當前資源狀態的方法:存在性(exists)、可讀性(isReadable)、是否處於開啟狀態(isOpen)。另外,Resource介面還提供了不同資源到URL、URI、File型別的轉換,以及獲取lastModified屬性、檔名(不帶路徑資訊的檔名,getFilename())的方法,為了便於操作,Resource還提供了基於當前資源建立一個相對資源的方法:createRelative(),還提供了getDescription()方法用於在錯誤處理中的列印資訊
對不同來源的資原始檔都有相應的Resource實現:檔案(FileSystemResource)、Classpath資源(ClassPathResource)、URL資源(UrlResource)、InputStream資源(InputStreamResource)、Byte陣列(ByteArrayResource)等,相關類圖如下所示
當通過Resource相關類完成了對配置檔案進行封裝後,配置檔案的讀取工作就全權交給XmlBeanDefinitionReader來處理了
XmlBeanFactory的初始化有若干辦法,Spring提供了很多的建構函式,在這裡分析的是使用Resource例項作為建構函式引數的辦法,程式碼如下
public XmlBeanFactory(Resource resource) throws BeansException {
//呼叫XmlBeanFactory(Resource,BeanFactory)構造方法
this(resource, (BeanFactory)null);
}
//parentBeanFactory為父類BeanFactory用於factory合併,可以為空
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
上面函式中的程式碼this.reader.loadBeanDefinitions(resource)才是資源載入的真正實現,時序圖中提到的XmlBeanDefinitionReader載入資料就是在這裡完成的,但是在XmlBeanDefinitionReader載入資料前還有一個呼叫父類建構函式初始化的過程:super(parentBeanFactory),跟蹤程式碼到父類AbstractAutowireCapableBeanFactory的建構函式中:
public AbstractAutowireCapableBeanFactory() {
super();
this.ignoreDependencyInterface(BeanNameAware.class);
this.ignoreDependencyInterface(BeanFactoryAware.class);
this.ignoreDependencyInterface(BeanClassLoaderAware.class);
}
- 1
- 2
- 3
- 4
- 5
- 6
ignoreDependencyInterface的主要功能是忽略給定介面的自動裝配功能,目的是:實現了BeanNameAware介面的屬性,不會被Spring自動初始化。自動裝配時忽略給定的依賴介面,典型應用是通過其他方式解析Application上下文註冊依賴,類似於BeanFactory通過BeanFactoryAware進行注入或者ApplicationContext通過ApplicationContextAware進行注入
載入Bean
在XmlBeanFactory建構函式中呼叫了XmlBeanDefinitionReader型別的reader屬性提供的方法this.reader.loadBeanDefinitions(resource),而這句程式碼則是整個資源載入的切入點,這個方法的時序圖如下
(1)封裝資原始檔。當進入XmlBeanDefinitionReader後首先對引數Resource使用EncodedResource類進行封裝
(2)獲取輸入流。從Resource中獲取對應的InputStream並構造InputSource
(3)通過構造的InputSource例項和Resource例項繼續呼叫函式doLoadBeanDefinitions,loadBeanDefinitions函式具體的實現過程:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource));
}
- 1
- 2
- 3
EncodedResource的作用是對資原始檔的編碼進行處理的,其中的主要邏輯體現在getReader()方法中,當設定了編碼屬性的時候Spring會使用相應的編碼作為輸入流的編碼
public Reader getReader() throws IOException {
return this.encoding != null?new InputStreamReader(this.resource.getInputStream(), this.encoding):new InputStreamReader(this.resource.getInputStream());
}
- 1
- 2
- 3
在構造好了encodeResource物件後,再次轉入了可複用方法loadBeanDefinitions(new EncodedResource(resource)),這個方法內部才是真正的資料準備階段
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if(this.logger.isInfoEnabled()) {
this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//通過屬性來記錄已經載入的資源
Object currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if(currentResources == null) {
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if(!((Set)currentResources).add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var6;
try {
//從encodedResource中獲取已經封裝的Resource物件並再次從Resource中獲取其中的InputStream
InputStream ex = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(ex);
if(encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//真正進入了邏輯核心部分
var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
ex.close();
}
} catch (IOException var15) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
} finally {
((Set)currentResources).remove(encodedResource);
if(((Set)currentResources).isEmpty()) {
this.resourcesCurrentlyBeingLoaded.set((Object)null);
}
}
return var6;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
資料準備階段的邏輯:首先對傳入的resource引數做封裝,目的是考慮到Resource可能存在編碼要求的情況,其次,通過SAX讀取XML檔案的方式來準備InputSource物件,最後將準備的資料通過引數傳入真正的核心處理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
int ex = this.getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, ex, this.isNamespaceAware());
return this.registerBeanDefinitions(doc, resource);
} catch (BeanDefinitionStoreException var5) {
throw var5;
} catch (SAXParseException var6) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);
} catch (SAXException var7) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);
} catch (ParserConfigurationException var8) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);
} catch (IOException var9) {
throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);
} catch (Throwable var10) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
在上面冗長的程式碼中假如不考慮異常類程式碼,其實只做了三件事
- 獲取對XML檔案的驗證模式
- 載入XML檔案,並得到對應的Document
- 根據返回的Document註冊Bean資訊
獲取XML的驗證模式
XML檔案的驗證模式保證了XML檔案的正確性,而比較常用的驗證模式有兩種:DTD和XSD
DTD和XSD區別
DTD(Document Type Definition)即文件型別定義,是一種XML約束模式語言,是XML檔案的驗證機制,屬於XML檔案組成的一部分。DTD是一種保證XML文件格式正確的有效方法,可以通過比較XML文件和DTD檔案來看文件是否符合規範,元素和標籤使用是否正確。一個DTD文件包含:元素的定義規則,元素間關係的定義規則,元素可使用的屬性,可使用的實體或符合規則
使用DTD驗證模式的時候需要在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">
...
- 1
- 2
- 3
而以Spring為例,具體的Spring-beans-2.0.dtd部分如下:
<!ELEMENT beans (
description?,
(import | alias | bean)*
)>
<!ATTLIST beans default-lazy-init (true | false) "false">
<!ATTLIST beans default-merge (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>
.....
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
XML Schema語言就是XSD(XML Schemas Definition)。XML Schema描述了XML文件的結構,可以用一個指定的XML Schema來驗證某個XML文件,以檢查該XML文件是否符合其要求,文件設計者可以通過XML Schema指定一個XML文件所允許的結構和內容,並可據此檢查一個XML文件是否是有效的
在使用XML Schema文件對XML例項文件進行檢驗,除了要宣告名稱空間外(xmlns=http://www.Springframework.org/schema/beans),還必須指定該名稱空間所對應的XML Schema文件的儲存位置,通過schemaLocation屬性來指定名稱空間所對應的XML Schema文件的儲存位置,它包含兩個部分,一部分是名稱空間的URI,另一部分就該名稱空間所標識的XML Schema檔案位置或URL地址(xsi:schemaLocation=”http://www.Springframework.org/schema/beans http://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/beans"
xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans.xsd">
....
</beans>
- 1
- 2
- 3
- 4
- 5
- 6
Spring-beans-3.0.xsd部分程式碼如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.springframework.org/schema/beans"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.springframework.org/schema/beans">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:annotation>
<xsd:documentation><![CDATA[
....
]]></xsd:documentation>
</xsd:annotation>
<!-- base types -->
<xsd:complexType name="identifiedType" abstract="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
The unique identifier for a bean. The scope of the identifier
is the enclosing bean factory.
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="id" type="xsd:ID">
<xsd:annotation>
<xsd:documentation><![CDATA[
The unique identifier for a bean.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
......
</xsd:schema>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
驗證模式的讀取
Spring通過getValidationModeForResource方法來獲取對應資源的驗證模式
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = this.getValidationMode();
//如果手動指定了驗證模式則使用指定的驗證模式
if(validationModeToUse != 1) {
return validationModeToUse;
} else {
//如果未指定則使用自動檢測
int detectedMode = this.detectValidationMode(resource);
return detectedMode != 1?detectedMode:3;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
自動檢測驗證模式的功能是在函式detectValidationMode方法中實現的,在detectValidationMode函式中又將自動檢測驗證模式的工作委託給了專門處理類XmlValidationModeDetector的detectValidationMode方法
protected int detectValidationMode(Resource resource) {
if(resource.isOpen()) {
throw new BeanDefinitionStoreException("Passed-in Resource [" + resource + "] contains an open stream: " + "cannot determine validation mode automatically. Either pass in a Resource " + "that is able to create fresh streams, or explicitly specify the validationMode " + "on your XmlBeanDefinitionReader instance.");
} else {
InputStream inputStream;
try {
inputStream = resource.getInputStream();
} catch (IOException var5) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + "Did you attempt to load directly from a SAX InputSource without specifying the " + "validationMode on your XmlBeanDefinitionReader instance?", var5);
}
try {
return this.validationModeDetector.detectValidationMode(inputStream);
} catch (IOException var4) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", var4);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
XmlValidationModeDetector類中的detectValidationMode方法如下
public int detectValidationMode(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
while(true) {
String content;
if((content = reader.readLine()) != null) {
content = this.consumeCommentTokens(content);
//如果讀取的行是空或者註釋則略過
if(this.inComment || !StringUtils.hasText(content)) {
continue;
}
if(this.hasDoctype(content)) {
isDtdValidated = true;
} else if(!this.hasOpeningTag(content)) {//讀取到<開始符號,驗證模式一定會會在開始符號之前
continue;
}
}
int var6 = isDtdValidated?2:3;
return var6;
}
} catch (CharConversionException var9) {
;
} finally {
reader.close();
}
return 1;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
Spring用來檢測驗證模式的辦法就是判斷是否包含DOCTYPE,如果包含就是DTD,否則就是XSD
獲取Document
經過了驗證模式準備的步驟就可以進行Document載入了,對於文件的讀取委託給了DocumentLoader去執行,這裡的DocumentLoader是個介面,而真正呼叫的是DefaultDocumentLoader,解析程式碼如下
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
if(logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
首選建立DocumentBuildFactory,再通過DocumentBuilderFactory建立DocumentBuilder,進而解析InputSource來返回Document物件。對於引數entityResolver,傳入的是通過getEntityResolver()函式獲取的返回值,程式碼如下
protected EntityResolver getEntityResolver() {
if(this.entityResolver == null) {
ResourceLoader resourceLoader = this.getResourceLoader();
if(resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
} else {
this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
}
}
return this.entityResolver;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
EntityResolver用法
如果SAX應用程式需要實現自定義處理外部實體,則必須實現此介面並使用setEntityResolver方法向SAX驅動器註冊一個例項。也就是說,對於解析一個XML,SAX首先讀取該XML文件上的宣告,根據宣告去尋找相應的DTD定義,以便對文件進行一個驗證,預設的尋找規則,即通過網路(實現上就是宣告DTD的URI地址)來下載相應的DTD宣告,並進行認證。下載的過程是一個漫長的過程,而且當網路中斷或不可用時,這裡會報錯,就是因為相應的DTD宣告沒有被找到的原因
EntityResolver的作用是專案本身就可以提供一個如何尋找DTD宣告的方法,即由程式來實現尋找DTD宣告的過程,比如將DTD檔案放到專案中某處,在實現時直接將此文件讀取並返回給SAX即可,entityResolver的介面方法宣告:
InputSource resolveEntity(String publicId, String systemId)
- 1
這裡,它接收兩個引數publicId和systemId,並返回一個InputSource物件,以特定配置檔案來進行講解
(1)如果在解析驗證模式為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>
- 1
- 2
- 3
- 4
- 5
- 6
讀取到以下兩個引數
(2)如果解析驗證模式為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>
- 1
- 2
- 3
- 4
讀取到以下兩個引數
一般都會把驗證檔案放置在自己的工程裡,如果把URL轉換為自己工程裡對應的地址檔案呢?以載入DTD檔案為例來看看Spring是如何實現的。根據之前Spring中通過getEntityResolver()方法對EntityResolver的獲取,我們知道,Spring中使用DelegatingEntityResolver類為EntityResolver的實現類,resolveEntity實現方法如下:
//DelegatingEntityResolver.java
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if(systemId != null) {
if(systemId.endsWith(".dtd")) {
//dtd從這裡解析
return this.dtdResolver.resolveEntity(publicId, systemId);
}
if(systemId.endsWith(".xsd")) {
//通過呼叫META-INF/Spring.schemas解析
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
對不同的驗證模式,Spring使用了不同的解析器解析,比如載入DTD型別的BeansDtdResolver的resolveEntity是直接擷取systemId最後的xx.dtd然後去當前路徑下尋找,而載入XSD型別的PluggableSchemaResolver類的resolveEntity是預設到META-INF/Spring.schemas檔案中找到systemId所對應的XSD檔案並載入
//BeansDtdResolver.java
public InputSource resolveEntity(String publicId, 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")) {
int lastPathSeparator = systemId.lastIndexOf("/");
String[] var7 = DTD_NAMES;
int var6 = DTD_NAMES.length;
for(int var5 = 0; var5 < var6; ++var5) {
String DTD_NAME = var7[var5];
// DTD_NAMES = {"Spring-beans-2.0", "Spring-beans"}
int dtdNameStart = systemId.indexOf(DTD_NAME);
if(dtdNameStart > lastPathSeparator) {
String dtdFile = systemId.substring(dtdNameStart);
if(logger.isTraceEnabled()) {
logger.trace("Trying to locate [" + dtdFile + "] in Spring jar");
}
try {
ClassPathResource ex = new ClassPathResource(dtdFile, this.getClass());
InputSource source = new InputSource(ex.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if(logger.isDebugEnabled()) {
logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
}
return source;
} catch (IOException var12) {
if(logger.isDebugEnabled()) {
logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in class path", var12);
}
}
}
}
}
return null;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
解析並註冊BeanDefinition
當把檔案轉換成Document後,接下來就是對bean的提取及註冊,當程式已經擁有了XML文件檔案的Document例項物件時,就會被引入下面這個方法
//XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//使用DefaultBeanDefinitionDocumentReader例項化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
//記錄統計之前BeanDefinition的載入個數
int countBefore = this.getRegistry().getBeanDefinitionCount();
//載入及註冊bean
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
//記錄本次載入的BeanDefinition個數
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
BeanDefinitionDocumentReader是一個介面,而例項化的工作是在createBeanDefinitionDocumentReader()中完成的,而通過此方法,BeanDefinitionDocumentReader真正的型別其實已經是DefaultBeanDefinitionDocumentReader了,進入DefaultBeanDefinitionDocumentReader後,發現這個方法的重要目的之一就是提取root,以便於再次將root作為引數繼續BeanDefinition的註冊
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
BeanDefinitionParserDelegate delegate = this.createHelper(readerContext, root);
//解析前處理,留給子類實現
this.preProcessXml(root);
this.parseBeanDefinitions(root, delegate);
//解析後處理,留給子類實現
this.postProcessXml(root);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
解析並註冊BeanDefinition
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//對beans的處理
if(delegate.isDefaultNamespace(delegate.getNamespaceURI(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;
String namespaceUri = delegate.getNamespaceURI(ele);
if(delegate.isDefaultNamespace(namespaceUri)) {
//對bean的處理
this.parseDefaultElement(ele, delegate);
} else {
//對bean的處理
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
在Spring的XML配置裡面有兩大類Bean宣告,一個是預設的,如:
<bean id="test" class="test.TestBean"/>
另一類就是自定義的,如:
<tx:annotation-driven>
- 1
而這兩種方式的讀取及解析差別是非常大的,如果採用Spring預設的配置,Spring當然知道該怎麼做,但如果是自定義的,那麼就需要使用者實現一些介面及配置了。對於根節點或子節點如果是預設名稱空間的話採用parseDefaultElement方法進行解析,否則使用delegate.parseCustomElement方法對自定義名稱空間進行解析。而判斷是否預設名稱空間還是自定義名稱空間的辦法其實是使用node.getNamespaceURI()獲取名稱空間,並與Spring中固定的名稱空間http://www.springframework.org/schema/beans進行對比,如果一致則認為是預設,否則就認為是自定義