亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

1. 概述

分析源碼是一件非常具有挑戰性的工作,在正是分析spring的源碼之前我們先來簡單回顧下spring核心功能的簡單使用

2. 容器的基本用法

bean是spring最核心的東西,spring就像是一個大水桶,而bean就是水桶中的水,水桶脫離了水也就沒有什么用處了,我們簡單看下bean的定義,代碼如下:

public class MyBeanDemo {
 private String beanName = "bean";

 public String getBeanName() {
 return beanName;
 }
 public void setBeanName(String beanName) {
 this.beanName = beanName;
 }
}

源碼很簡單,bean沒有特別之處,spring的的目的就是讓我們的bean成為一個純粹的的POJO,這就是spring追求的,接下來就是在配置文件中定義這個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中屬性,但是我們只要像上面這樣簡單的聲明就可以使用了。 
具體測試代碼如下:

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();
 }
 }
}

運行上述測試代碼就可以看到輸出結果如下圖: 

spring源碼深度解析—容器的基本實現,你知多少?

 


其實直接使用BeanFactory作為容器對于Spring的使用并不多見,因為企業級應用項目中大多會使用的是ApplicationContext(后面我們會講兩者的區別,這里只是測試)

3. 功能分析

接下來我們分析2中代碼完成的功能; 
- 讀取配置文件spring-bean.xml。 
- 根據spring-beanxml中的配置找到對應的類的配置,并實例化。 
- 調用實例化后的實例 
下圖是一個最簡單spring功能架構,如果想完成我們預想的功能,至少需要3個類: 

spring源碼深度解析—容器的基本實現,你知多少?

 


其中, 
ConfigReader:用于讀取及驗證配置文件。我們要用配置文件里面的東西,當然首先要做的就是讀取,然后放置在內存中。 
ReflectionUtil:用于根據配置文件中的配置進行反射實例化。比如在例2.1中spring-bean.xml出現的 
我們就可以根據bean.demo進行實例化。 
APP:用于完成整個邏輯的串聯。

4. 工程搭建

在spring的源碼中用于實現上面功能的是spring-bean這個工程,所以我們接下來看這個工程,當然spring-core是必須的。

4.1 beans包的層級結構

閱讀源碼最好的方式是跟著示例操作一遍,我們先看看beans工程的源碼結構,如下圖所示: 

spring源碼深度解析—容器的基本實現,你知多少?

 


- src/main/JAVA 用于展現Spring的主要邏輯 
- src/main/resources 用于存放系統的配置文件 
- src/test/java 用于對主要邏輯進行單元測試 
- src/test/resources 用于存放測試用的配置文件

4.2 核心類介紹

接下來我們先了解下spring-bean最核心的兩個類:DefaultListableBeanFactory和XmlBeanDefinitionReader 
4.2.1 DefaultListableBeanFactory 
XmlBeanFactory繼承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整個bean加載的核心部分,是Spring注冊及加載bean的默認實現,而對于XmlBeanFactory與DefaultListableBeanFactory不同的地方其實是在XmlBeanFactory中使用了自定義的XML讀取器XmlBeanDefinitionReader,實現了個性化的BeanDefinitionReader讀取,DefaultListableBeanFactory繼承了AbstractAutowireCapableBeanFactory并實現了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。以下是ConfigurableListableBeanFactory的層次結構圖以下相關類圖, 

spring源碼深度解析—容器的基本實現,你知多少?

 


上面類圖中各個類及接口的作用如下: 
- 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屬性對資源文件進行讀取和注冊 
4.2.2 XmlBeanDefinitionReader 
XML配置文件的讀取是Spring中重要的功能,因為Spring的大部分功能都是以配置作為切入點的,可以從XmlBeanDefinitionReader中梳理一下資源文件讀取、解析及注冊的大致脈絡,先看看各個類的功能

ResourceLoader:定義資源加載器,主要應用于根據給定的資源文件地址返回對應的Resource 
BeanDefinitionReader:主要定義資源文件讀取并轉換為BeanDefinition的各個功能 
EnvironmentCapable:定義獲取Environment方法 
DocumentLoader:定義從資源文件加載到轉換為Document的功能 
AbstractBeanDefinitionReader:對EnvironmentCapable、BeanDefinitionReader類定義功能進行實現 
BeanDefinitionDocumentReader:定義讀取Document并注冊BeanDefinition功能 
BeanDefinitionParserDelegate:定義解析Element的各種方法 
整個XML配置文件讀取的大致流程,在XmlBeanDefinitionReader中主要包含以下幾步處理 

spring源碼深度解析—容器的基本實現,你知多少?

 


(1)通過繼承自AbstractBeanDefinitionReader中的方法,來使用ResourceLoader將資源文件路徑轉換為對應的Resource文件 
(2)通過DocumentLoader對Resource文件進行轉換,將Resource文件轉換為Document文件 
(3)通過實現接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader類對Document進行解析,并使用BeanDefinitionParserDelegate對Element進行解析

5. 容器的基礎XmlBeanFactory

通過上面的內容我們對spring的容器已經有了大致的了解,接下來我們詳細探索每個步驟的詳細實現,接下來要分析的功能都是基于如下代碼:

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-bean.xml"));

通過XmlBeanFactory初始化時序圖看一看上面代碼的執行邏輯,如下圖所示: 

spring源碼深度解析—容器的基本實現,你知多少?

 


時序圖從TestDemo測試類開始,首先調用ClassPathResource的構造函數來構造Resource資源文件的實例對象,這樣后續的資源處理就可以用Resource提供的各種服務來操作了。有了Resource后就可以對BeanFactory進行初始化操作,那配置文件是如何封裝的呢? 
5.1 配置文件的封裝 
Spring的配置文件讀取是通過ClassPathResource進行封裝的,Spring對其內部使用到的資源實現了自己的抽象結構: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等, 它只有一個方法定義: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)等,相關類圖如下所示: 

spring源碼深度解析—容器的基本實現,你知多少?

 


在日常開發中我們可以直接使用spring提供的類來加載資源文件,比如在希望加載資源文件時可以使用下面的代碼:

Resource resource = new ClassPathResource("spring-bean.xml");
InputStream is = resource.getInputStream();

當通過Resource相關類完成了對配置文件進行封裝后,配置文件的讀取工作就全權交給XmlBeanDefinitionReader來處理了。 
接下來就進入到XmlBeanFactory的初始化過程了,XmlBeanFactory的初始化有若干辦法,Spring提供了很多的構造函數,在這里分析的是使用Resource實例作為構造函數參數的辦法,代碼如下:

 public XmlBeanFactory(Resource resource) throws BeansException {
 this(resource, null);
 }
 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
 super(parentBeanFactory);
 this.reader.loadBeanDefinitions(resource);
}

上面函數中的代碼this.reader.loadBeanDefinitions(resource)才是資源加載的真正實現,時序圖中提到的XmlBeanDefinitionReader加載數據就是在這里完成的,但是在XmlBeanDefinitionReader加載數據前還有一個調用父類構造函數初始化的過程:super(parentBeanFactory),我們按照代碼層級進行跟蹤,首先跟蹤到如下父類代碼:

public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
 super(parentBeanFactory);
}

然后繼續跟蹤,跟蹤代碼到父類AbstractAutowireCapableBeanFactory的構造函數中:

public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
 this();
 setParentBeanFactory(parentBeanFactory);
}
public AbstractAutowireCapableBeanFactory() {
 super();
 ignoreDependencyInterface(BeanNameAware.class);
 ignoreDependencyInterface(BeanFactoryAware.class);
 ignoreDependencyInterface(BeanClassLoaderAware.class);
}

這里要提及一下ignoreDependencyInterface方法,此方法的主要功能是忽略給定接口的自動裝配功能,目的是:實現了BeanNameAware接口的屬性,不會被Spring自動初始化。自動裝配時忽略給定的依賴接口,典型應用是通過其他方式解析Application上下文注冊依賴,類似于BeanFactory通過BeanFactoryAware進行注入或者ApplicationContext通過ApplicationContextAware進行注入。 
5.2 bean加載 
在之前XmlBeanFactory構造函數中調用了XmlBeanDefinitionReader類型的reader屬性提供的方法this.reader.loadBeanDefinitions(resource),而這句代碼則是整個資源加載的切入點,這個方法的時序圖如下: 

spring源碼深度解析—容器的基本實現,你知多少?

 


我們來梳理下上述時序圖的處理過程: 
(1)封裝資源文件。當進入XmlBeanDefinitionReader后首先對參數Resource使用EncodedResource類進行封裝 
(2)獲取輸入流。從Resource中獲取對應的InputStream并構造InputSource 
(3)通過構造的InputSource實例和Resource實例繼續調用函數doLoadBeanDefinitions,loadBeanDefinitions函數具體的實現過程: 

spring源碼深度解析—容器的基本實現,你知多少?

 


EncodedResource的作用是對資源文件的編碼進行處理的,其中的主要邏輯體現在getReader()方法中,當設置了編碼屬性的時候Spring會使用相應的編碼作為輸入流的編碼,在構造好了encodeResource對象后,再次轉入了可復用方法loadBeanDefinitions(new EncodedResource(resource)),這個方法內部才是真正的數據準備階段,代碼如下:

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);
 }
}

在上面冗長的代碼中假如不考慮異常類代碼,其實只做了三件事 
- 獲取對XML文件的驗證模式 
- 加載XML文件,并得到對應的Document 
- 根據返回的Document注冊Bean信息 
5.3 獲取XML的驗證模式 
XML文件的驗證模式保證了XML文件的正確性,而比較常用的驗證模式有兩種:DTD和XSD 
5.3.1 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">

而以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文檔的結構,可以用一個指定的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/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部分代碼如下: 

spring源碼深度解析—容器的基本實現,你知多少?

 


5.3.2 驗證模式的讀取 
在spring中,是通過getValidationModeForResource方法來獲取對應資源的驗證模式,其源碼如下:

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;
}

方法的實現還是很簡單的,如果設定了驗證模式則使用設定的驗證模式(可以通過使用XmlBeanDefinitionReader中的setValidationMode方法進行設定),否則使用自動檢測的方式。而自動檢測驗證模式的功能是在函數detectValidationMode方法中,而在此方法中又將自動檢測驗證模式的工作委托給了專門處理類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用來檢測驗證模式的辦法就是判斷是否包含DOCTYPE,如果包含就是DTD,否則就是XSD

5.4. 獲取Document

經過了驗證模式準備的步驟就可以進行Document加載了,對于文檔的讀取委托給了DocumentLoader去執行,這里的DocumentLoader是個接口,而真正調用的是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);
}

分析代碼,首選創建DocumentBuildFactory,再通過DocumentBuilderFactory創建DocumentBuilder,進而解析InputSource來返回Document對象。對于參數entityResolver,傳入的是通過getEntityResolver()函數獲取的返回值,代碼如下:

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;
}

這個entityResolver是做什么用的呢,接下來我們詳細分析下。 
5.4.1 EntityResolver 的用法 
如果SAX應用程序需要實現自定義處理外部實體,則必須實現此接口并使用setEntityResolver方法向SAX驅動器注冊一個實例。也就是說,對于解析一個XML,SAX首先讀取該XML文檔上的聲明,根據聲明去尋找相應的DTD定義,以便對文檔進行一個驗證,默認的尋找規則,即通過網絡(實現上就是聲明DTD的URI地址)來下載相應的DTD聲明,并進行認證。下載的過程是一個漫長的過程,而且當網絡中斷或不可用時,這里會報錯,就是因為相應的DTD聲明沒有被找到的原因 
EntityResolver的作用是項目本身就可以提供一個如何尋找DTD聲明的方法,即由程序來實現尋找DTD聲明的過程,比如將DTD文件放到項目中某處,在實現時直接將此文檔讀取并返回給SAX即可,在EntityResolver的接口只有一個方法聲明:

public abstract InputSource resolveEntity (String publicId, String systemId)
 throws SAXException, IOException;

它接收兩個參數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>

則會讀取到以下兩個參數 
- publicId:null 
- systemId:http://www.Springframework.org/schema/beans/Spring-beans.xsd 
(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>

讀取到以下兩個參數 
- publicId:-//Spring//DTD BEAN 2.0//EN 
- systemId:http://www.Springframework.org/dtd/Spring-beans-2.0.dtd 
一般都會把驗證文件放置在自己的工程里,如果把URL轉換為自己工程里對應的地址文件呢?以加載DTD文件為例來看看Spring是如何實現的。根據之前Spring中通過getEntityResolver()方法對EntityResolver的獲取,我們知道,Spring中使用DelegatingEntityResolver類為EntityResolver的實現類,resolveEntity實現方法如下:

@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;

對不同的驗證模式,Spring使用了不同的解析器解析,比如加載DTD類型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去當前路徑下尋找,而加載XSD類型的PluggableSchemaResolver類的resolveEntity是默認到META-INF/Spring.schemas文件中找到systemId所對應的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 解析及注冊BeanDefinitions

當把文件轉換成Document后,接下來就是對bean的提取及注冊,當程序已經擁有了XML文檔文件的Document實例對象時,就會被引入到XmlBeanDefinitionReader.registerBeanDefinitions這個方法:

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參數即為上節讀取的document,而BeanDefinitionDocumentReader是一個接口,而實例化的工作是在createBeanDefinitionDocumentReader()中完成的,而通過此方法,BeanDefinitionDocumentReader真正的類型其實已經是DefaultBeanDefinitionDocumentReader了,進入DefaultBeanDefinitionDocumentReader后,發現這個方法的重要目的之一就是提取root,以便于再次將root作為參數繼續BeanDefinition的注冊,如下代碼:

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聲明,一個是默認的,如: 
另一類就是自定義的,如: 
而這兩種方式的讀取及解析差別是非常大的,如果采用Spring默認的配置,Spring當然知道該怎么做,但如果是自定義的,那么就需要用戶實現一些接口及配置了。對于根節點或子節點如果是默認命名空間的話采用parseDefaultElement方法進行解析,否則使用delegate.parseCustomElement方法對自定義命名空間進行解析。而判斷是否默認命名空間還是自定義命名空間的辦法其實是使用node.getNamespaceURI()獲取命名空間,并與Spring中固定的命名空間http://www.springframework.org/schema/beans進行對比,如果一致則認為是默認,否則就認為是自定義。 
profile的用法 
通過profile標記不同的環境,可以通過設置spring.profiles.active和spring.profiles.default激活指定profile環境。如果設置了active,default便失去了作用。如果兩個都沒有設置,那么帶有profiles的bean都不會生成。 
有多種方式來設置這兩個屬性:

作為DispatcherServlet的初始化參數;
作為web應用的上下文參數;
作為JNDI條目;
作為環境變量; System.set("spring.profiles.active","prod")
作為JVM的系統屬性; -Dspring.profiles.active="prod"
在集成測試類上,使用@ActiveProfiles注解配置。

以前兩種方式舉例,它們都可以在web.xml中設置:

<?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>
 <!-- 在上下文中設置profile的默認值 -->
 <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中設置profile的默認值 -->
 <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>

分享到:
標簽:spring
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定