天天看點

Spring源碼深度解析第1部分 核心實作

第1部分 核心實作

第一章 Spring整體架構和環境搭建

1.1 Spring的整體架構

1. Spring架構是一個分層架構,它包含一系列的功能要素,并被分成大約20個子產品。這些子產品被總結為以下幾部分。
	1. Core Contrainer
		1. Core Contrainer(核心容器)包含有Core、Beans、Context和Expression Language子產品。
		2. Core和Beans子產品是架構的基礎部分,提供IoC(轉控制)和依賴注入特性。這裡的基礎概念是BeanFactory,它提供對Factory模式的經典實作來消除對程式單例模式的需求,并真正允許你從程式邏輯中分離出依賴關系和配置。
			1. Core子產品主要包含Spring架構基本的核心工具類,Spring的其它元件都要用到這個包裡的類,Core子產品是其它元件的基本核心。當然你也可以在自己的應用系統中使用這些工具類。
			2. Beans子產品是所有應用都要用到的,它包含通路配置檔案、建立和管理bean以及進行Inversion of Control/Dependency Injection(IoC/DI)操作相關的所有類
			3. Context子產品建構于Core和Beans子產品基礎之上,提供了一種類似于JNDI注冊器的架構式對象通路方法。Context子產品繼承了Beans的特性,為Spring核心提供了大量擴充,添加了對國際化(例如資源綁定)、事件傳播、資源加載和對Context的透明建立的支援。Context子產品同時也支援J2EE的一些特性,例如EJB、JMX和基礎的遠端處理。ApplicationContext接口是Context子產品的關鍵。
			4. Expression Language子產品提供了強大的表達式語言,用于在運作時查詢和操作對象。它是JSP2.1規範中定義的unifed expression language的擴充。該語言支援設定/擷取屬性的值,屬性的配置設定,方法的調用,通路數組上下文(accession the context of arrays)、容器和索引器、邏輯和算數運算符、命名變量以及從Spring的IoC容器中根據名稱檢索對象。它也支援list投影、選擇和一般的list聚合。
			5. **就看懂了1、2點,3、4點目前還沒接觸到,是以沒什麼了解。**
	2. Data Access/Integration
		1. Data Access/Integration層包含JDBC、ORM、OXM、JMS和Transaction子產品
			1. JDBC子產品提供了一個JDBC抽象層,它可以消除冗長的JDBC編碼和解析資料庫廠商特有的錯誤代碼。這個子產品包含了Spring對JDBC資料通路進行封裝的所有類。
			2. ORM子產品為流行的對象-關系映射API,如JPA、JDO、Hibernate、iBatis等,提供了一個互動層。利用ORM封裝包,可以混合使用所有Spring提供的特性進行O/R映射,如前邊提到的簡單聲明性事務管理。
			3. **Spring架構插入了若幹個ORM架構,進而提供了ORM的對象關系工具,其中包括JDO、Hibernate和iBatisSQL Map。所有這些都遵從Spring的通用事務和DAO異常層次結構。**
			4. OXM子產品提供了一個對Object/XML映射實作的抽象層,Object/XML映射實作包括JAXB、Castor、XMLBeans、JiBX和XStream。
			5. JMS(Java Messagin Service)子產品主要包含了一些制造和消費消息的特性。
			6. Transaction子產品支援程式設計和聲明性的事務管理,這些事務類必須實作特定的接口,并且對所有的POJO都适用。
			7. 對4、5點不了解。
	3. Web
		1. Web上下文子產品建立在應用程式上下文子產品之上,為基于Web的應用程式提供了上下文。是以,Spring架構支援與Jakarta Struts的內建。Web子產品還簡化了處理大部分請求以及将請求參數綁定到域對象的工作。Web層包含了Web、Web-Servlet、Web-Struts和Web-Porlet子產品,具體說明如下。
			1. Web子產品:提供了基礎的面向Web的內建特性。例如,多檔案上傳、使用servlet listeners初始化IoC容器以及一個面向Web的應用上下文。它還包含Spring遠端支援中Web的相關部分。
			2. Web-Servlet子產品web.servlet.jar:該子產品包含Spring的model-view-controller(MVC)實作。Spring的MVC架構使得模型範圍内的代碼和web forms之間能夠清楚地分離開來,并與Spring架構地其它特性內建在一起。
			3. Web-Struts子產品:該子產品提供了對Struts的支援,使得類在Spring應用中能夠與一個典型的Struts Web層內建在一起。**注意,該支援在Spring3.0中已被棄用**。
			4. Web-Porlet子產品:提供了用于Portlet環境和Web-Servlet子產品的MVC的實作。
			5. 感覺這些都很陌生。
	4. AOP
		1. AOP子產品提供了一個符合AOP聯盟标準的面向切面程式設計的實作,它讓你可以定義例如方法攔截器和切點,進而将邏輯代碼分開,降低它們之間的耦合性。利用source-level的中繼資料功能,還可以将各種行為資訊合并到你的代碼中,這點有點像.Net技術中的attribute概念。
		2. 通過配置管理特性,Spring AOP子產品直接将面向切面的程式設計功能內建到了Spring架構中,是以可以很容易地使Spring架構管理的任何對象支援AOP。Spring AOP子產品為基于Spring的應用程式中的對象提供了事務管理服務。通過使用Spring AOP,不用依賴EJB元件,就可以将聲明式事務管理內建到應用程式中。
			1. Aspects子產品提供了對AspectJ的內建支援。
			2. Instrumentation子產品提供了class instrumentation支援和classloader實作,使得可以在特定的應用伺服器上使用。
	5. Test
		1. Test子產品支援使用JUnit和TestNG對Spring元件進行測試。
           

1.2 環境搭建

1. 由于選取的idea版本及spring的版本不同,會出現不同的錯誤,不在此處浪費時間。
           

1.3 cglib和objenesis的編譯錯誤解決

1. 我未遇到此問題。
           

第二章 容器的基本實作

1. 源碼分析是一件非常煎熬且極具挑戰性的任務,你準備好開始戰鬥了嗎?
           

2.1 容器的基本用法

1. bean是Spring中最核心的東西,因為Spring就像是個大水桶,而bean就像是容器中的水,水桶脫離了水便也沒什麼用處了,那麼我們先來看看bean的定義。 
2. 基本用法就是将bean聲明式注冊到xml檔案中,并通過Factory取出bean。不過多贅述。
           

2.2 功能分析

1. 現在我們可以來好好分析一下上面測試代碼的功能,來探索上面的代碼中Spring究竟幫我們完成了什麼工作?不管你之前是否使用過Spring,當然,你都開始關心底層了,你應當使用過。上面那段代碼完成的功能無非就是以下幾點:
	1. 讀取配置檔案beanFactoryTest.xml
	2. 根據beanFactoryTest.xml中的配置找到對應類的配置,并執行個體化。
	3. 調用執行個體化後的執行個體。
2. 為了更清楚地描述,如果想要完成我們預想地功能,至少需要3個類:
	1. ConfigReader:用于讀取及驗證配置檔案。我們要用配置檔案裡面的東西,雖然首先要做的就是讀取,然後放置在記憶體中。
	2. ReflectionUtil:用于根據配置檔案中地配置進行反射執行個體化。比如在上例中beanFactoryTest.xml出現地<bean id="myTestBean" class="bean.MyTestBean"/>,我們就可以根據bean.MyTestBean進行執行個體化。
	3. App:用于完成整個邏輯地串聯。
3. 按照原始地思維方式,整個過程無非如此,但是不幸地是,因為各代大佬地優化,這個過程變得非常複雜。
           

2.3 工程搭建

1. 在Spring源碼中,用于實作上面功能地是**org.Springframework.beans.jar**
           

2.4 Spring地結構組成

1. 先嘗試梳理Spring的架構結構,從全局的角度了解到Spring的結構組成。
           

2.4.1 beans包的層級結構

1. 先看看整個beans工程的源碼結構。
	1. src/main/java用于展現Spring的主要配置
	2. src/main/resources用于存放系統的配置檔案
	3. src/test/java 用于對主要邏輯進行單元測試
	4. src/test/resources用于存放測試用的配置檔案
           

2.4.2 核心類介紹

1. 通過beans工程的結構介紹,我們現在對beans的工程結構有了初步的認識,但是在正式開始源碼分析之前,有必要了解Spring中核心的兩個類。
	1. DefaultListableBeanFactory
		1. XmlBeanFactory繼承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整個bean加載的核心部分,是Spring注冊及加載bean的預設實作,而對于XmlBeanFactory與DefaultListableBeanFactory不同的地方其實是在XmlBeanFactory中使用了自定義的XML讀取器XmlBeanDefinitionReader,實作了個性化的BeanDefinitionReader讀取,DefaultListableBeanFactory繼承了AbstractAutowireCapableBeanFactory并實作了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。
		2. 簡單了解類圖中各個類的作用
			1. AliasRegistry:定義對alias(别名)的簡單增删改查。
			2. SimpleAliasRegistry:主要使用map作為alias的緩存,并對接口AliasRegistry進行實作。
			3. SingletonBeanRegistry:定義對單例的注冊及擷取。
			4. BeanFactory:定義擷取bean及bean的各種屬性。
			5. DefaultSingletonBeanRegistry:對接口SingetonBeanRegistry各函數的實作。
			6. HierarchicalBeanFactory:繼承BeanFactory,也就是在BeanFactory定義的功能的基礎上增加了對parentFactory的支援。
			7. BeanDefinitionRegistry:定義對BeanDefintion的各種增删改查操作。
			8. FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基礎上增加了對FactoryBean的特殊處理功能。
			9. ConfigurableBeanFactory:提供配置Factory的各種方法。
			10. ListableBeanFactory:根據各種條件擷取bean的配置清單。
			11. AbstractBeanFactory:綜合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能。
			12. AutowireCapableBeanFactory:提供建立bean、自動注入、初始化以及應用bean的後處理器。
			13. AbstractAutowireCapableBeanFactory:綜合AbstractBeanFactory并對接口Autowire CapableBeanFactory進行實作。
			14. ConfigurableListableBeanFactory:BeanFactory配置清單,指定忽略類型及接口等。
			15. DefaultListableBeanFactory:綜合上面所有功能,主要對bean注冊後的處理。
			16. 上面這個隻是冰山一角。**快跑**
		3. XmlBeanFactory對DefaultListableBeanFactory類進行了擴充,主要用于從XML文檔中讀取BeanDefinition,對于注冊及擷取bean都是使用從父類DefaultListableBeanFactory繼承的方法去實作,而唯獨與父類不同的個性化實作就是增加了XmlBeanDefinitionReader類型的reader屬性。在XmlBeanFactory中主要使用reader屬性對資源檔案進行讀取和注冊。
2. XmlBeanDefinitionReader
		1. XML配置檔案的讀取是Spring中重要的功能,因為Spring的大部分功能都是以配置作為切入點的,那麼我們可以從XmlBeanDefinitionReader中梳理一下資源檔案讀取、解析及注冊的大緻脈絡,首先我們看看各個類的功能。
			1. ResourceLoader:定義資源加載器,主要應用于根據給定的資源檔案位址傳回對應的Resource。
			2. BeanDefinitionReader:主要定義資源檔案讀取并轉換為BeanDefinition的各個功能。
			3. EnvironmentCapable:定義擷取Environment方法。
			4. DocumentLoader:定義從資源檔案加載到轉換為Document的功能。
			5. AbstractBeanDefinitionReader:對EnvironmentCapable、BeanDefinitionReader類定義的功能進行實作。
			6. BeanDefinitionDocumentReader:定義讀取Document并注冊BeanDefinition功能。
			7. BeanDefinitionParserDelegate:定義解析Element的各種方法。
			8. 經過上面的分析,我們可以梳理出整個XML配置檔案讀取的大緻流程。
				1. 通過繼承自AbstractBeanDefinitionReader中的方法,來使用ResourceLoader将資源檔案路徑轉換為對應的Resource檔案。
				2. 通過DocumentLoader對Resource檔案進行轉換,将Resource檔案轉換為Document檔案。
				3. 通過實作接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader類對Document進行解析,并使用BeanDenifitionParserDelegte對Element進行解析。
           

2.5 容器的基礎XmlBeanFactory

1. 我們接下來深入分析一下功能的代碼實作
	1. BeanFactory bf = new XmlBeanFactory(new ClassPathResource(new ClassPathResource("beanFactoryTest.xml")))
2. 上述代碼的執行順序為:在測試的BeanFactoryTest中首先調用ClassPathResource的構造函數來構造Resource資源檔案的執行個體對象,這樣後續的資源處理就可以用Resource提供的各種服務來操作了,當我們有了Resource後就可以進行XmlBeanFactory的初始化。那麼Resource資源是如何封裝的呢?
           

2.5.1 配置檔案封裝

1. Spring的配置檔案讀取是通過ClassPathResource進行封裝的,如new ClassPathResource("beanFactoryTest.xml"),那麼ClassPathResource完成了什麼功能呢?
2. 在Java中,将不同來源的資源抽象成URL,通過注冊不同的handler(URLStreamHandler)來處理不同來源的資源的讀取邏輯,一般handler的類型使用不同字首(協定,Protocol)來識别,如"file:" "http:" "jar:"等,然而URL沒有預設定義相對Classpath或ServletContext等資源的handler,雖然可以注冊自己的URLStreamHandler來解析特定的URL字首(協定),比如"classpath:",然而這需要了解URL的實作機制,而且URL也沒有提供基本的方法,如檢查目前資源是否存在、檢查目前資源是否可讀等方法。因而Spring對其内部使用到的資源實作了自己的抽象結構:Resource接口封裝底層資源。
```
	public interface InputStreamSource {
		InputStream getInputStream() throws IOException;
	
	}
```
```
	public interface Resource extends InputStreamSource {

		boolean exists();
	
		default boolean isReadable() {
			return exists();
		}
	
		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;
	
		@Nullable
		String getFilename();
	
		String getDescription();
	
	}

```
3. InputStreamSource封裝任何能傳回InputStream的類,比如File、URL、Classpath下的資源和ByteArray等。它隻有一個方法定義:getInputStream(),該方法傳回一個新的InputStream對象。
4. Resource接口抽象了所有Spring内部使用到的底層資源:File、URL、Classpath等。首先,它定義了3個判斷目前資源狀态的方法:存在性(exists)、可讀性(isReadable)、是否處于打開狀态(isOpen)。另外,Resource接口還提供了不同資源到URL、URI、File類型的轉換,以及擷取lastModified屬性、檔案名(不帶路徑資訊的檔案名,getFilename())的方法。為了便于操作,Resource還提供了基于目前資源建立一個相對資源的方法:createRelatice()。在錯誤進行中需要詳細的列印出錯的資源檔案,因而Resource還提供了getDescription()方法用來在錯誤進行中列印資訊。
5. 對不同來源的資源檔案都有相應的Resource實作:檔案(FileSystemResource)、Classpath資源(ClassPathResource)、URL資源(UrlResource)、InputStream資源(InputStreamResource)、Byte數組(ByteArrayResource)等。
6. 當通過Resource相關類完成了對配置檔案進行封裝後配置檔案的讀取工作就全權交給XmlBeanDefinitionReader來處理了。
7. 了解了Spring中将配置檔案封裝為Resource類型的執行個體方法後,我們就可以繼續探尋XmlBeanFactory的初始化過程了,XmlBeanFactory的初始化有若幹辦法,Spring中提供了很多的構造函數,在這裡分析的是使用Resource執行個體作為構造函數參數的辦法,代碼如下:
```
	XmlBeanFactory.java
	public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}
```
8. 構造函數内部再次調用内部構造函數
```
	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		this.reader.loadBeanDefinitions(resource);
	}
```
9. 上面函數中的代碼this.reader.loadBeanDefinitions(resource)才是資源加載真正的實作,也是我們分析的重點之一。XmlBeanDefinitionReader加載資料就是在這裡完成的,但是在XmlBeanDefinitionReader加載資料前還有一個調用父類構造函數初始化的過程:
	1. super(parentBeanFactory),跟蹤代碼到父類AbstractAutowireCapableBeanFactory的構造函數中:
	```
		public AbstractAutowireCapableBeanFactory() {
		super();
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
	}
	```
10. 這裡有必要提及ignoreDependencyInterface方法。ignoreDependencyInterface的主要功能是忽略給定接口的自動裝配功能,那麼,這樣做的目的是什麼呢?會産生什麼樣的效果呢?
	1. 當A中有屬性B,那麼當Spring在擷取A的Bean的時候如果其屬性B還沒有初始化,那麼Spring會自動化初始化B,這也是Spring中提供的一個重要特性。但是,某些情況下,B不會被初始化,其中的一種情況就是B實作了BeanNameAware接口。Spring中是這樣介紹的:**自動裝配時忽略給定的依賴接口,**典型應用是通過其他方式解析Application上下文注冊依賴,類似于BeanFactory通過BeanFactoryAware進行注入或者ApplicationContext通過ApplicationContextAware進行注入。
           

2.5.2 加載Bean

1. 之前提到的在XmlBeanFactory構造函數中調用了XmlBeanDefinitionReader類型的reader屬性提供的方法"this.reader.loadBeanDefinitions(resource)",而這句代碼則是整個資源加載的切入點。
2. 整個處理過程如下:
	1. 封裝資源檔案。當進入XmlBeanDefinitionReader後首先對參數Resource使用EncodeResource類進行封裝。
	2. 擷取輸入流。從Resource中擷取對應的InputStream并構造InputSource。
	3. 通過構造的InputSource執行個體和Resource執行個體繼續調用函數doLoadBeanDefinitions。
3. 我們先來看下loadBeanDefinitions()函數的具體實作過程。
```
	@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	return loadBeanDefinitions(new EncodedResource(resource));
}
```
4. 那麼EncodedResource的作用是什麼呢?通過名稱,我們可以大緻推斷這個類主要是用于對資源檔案的編碼進行處理。其中的主要邏輯展現在getReader()方法中,當設定了編碼屬性的時候Spring會使用相應的編碼作為輸入流的編碼。
```
	public Reader getReader() throws IOException {
		if (this.charset != null) {
		return new InputStreamReader(this.resource.getInputStream(), this.charset);
	}
		else if (this.encoding != null) {
		return new InputStreamReader(this.resource.getInputStream(), this.encoding);
	}
		else {
		return new InputStreamReader(this.resource.getInputStream());
	}
}
```
5. 上面構造了一個有編碼(encoding)的InputStreamReader。當構造好encodedResource對象後,再次轉入了可複用方法loadBeanDefinitions(new EncodedResource(resource))。
6. 這個方法内部才是真正的資料準備階段:
```
	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isTraceEnabled()) {
		logger.trace("Loading XML bean definitions from " + encodedResource);
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

		if (!currentResources.add(encodedResource)) {
		throw new BeanDefinitionStoreException(
				"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}

		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
			inputSource.setEncoding(encodedResource.getEncoding());
			}
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
				"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}
```
7. 我們再次整理資料準備階段的邏輯,首先對傳入的resource參數做封裝,目的是考慮到Resource可能存在編碼要求的情況,其次,通過SAX讀取XML檔案的方式來準備InputSource對象,最後将準備的資料通過參數傳入真正核心處理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())。
```
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {

		try {
			Document doc = doLoadDocument(inputSource, resource);
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		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);
		}
	}
```
8. 上面冗長的代碼中假如不考慮異常類的代碼,其實隻做了三件事,這三件事的每一件都必不可少:
	1. 擷取對XML檔案的驗證模式。
	2. 加載XML檔案,并得到對應的Document。
	3. 根據傳回的Document注冊Bean資訊。
9. 這3個步驟支撐着整個Spring容器部分的實作,尤其是第3步對配置檔案的解析,邏輯非常的複雜,我們先從擷取XML檔案的驗證模式講起。
           

2.6 擷取XML的驗證模式

1. 了解XML檔案都應該知道XML檔案的驗證模式保證了XML檔案的正确性,而比較常用的驗證模式有兩種:DTD和XSD。它們之間有什麼差別呢?
           

2.6.1 DTD與XSD差別

1. 略,有興趣的可以自己去w3c看。
           

2.6.2 驗證模式的讀取

1. 了解了DTD與XSD的差別後我們再去分析Spring中對于驗證模式的提取就更容易了解了。通過之前的分析我們鎖定了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;
	}
```
2. 方法的實作其實還是很簡單的,無非是如果設定了驗證模式則使用設定的驗證模式(可以通過對調用XmlBeanDefinitionReader中的setValidationMode方法進行設定),否則使用自動檢測的方式。而自動檢測驗證模式的功能是在函數detectValidationMode()方法中實作的,在detectValidationMode()函數中又将自動檢測驗證模式的工作委托給了專門處理類XmlValidationModeDetector,調用了XmlValidationModeDetector的validationModeDetector方法,集體代碼如下:
           
XmlBeanDefinitionReader.java
```
	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.");
		}

		InputStream inputStream;
		try {
			inputStream = resource.getInputStream();
		}
		catch (IOException ex) {
			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?", ex);
		}

		try {
			return this.validationModeDetector.detectValidationMode(inputStream);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
					resource + "]: an error occurred whilst reading from the InputStream.", ex);
		}
	}
```