Spring原始碼剖析——核心IOC容器原理
IOC介紹
相信大多數人在學習Spring時 IOC 和 Bean 算得上是最常聽到的兩個名詞,IOC在學習Spring當中出現頻率如此之高必然有其原因。如果我們做一個比喻的話,把Bean說成Spring中主角的話,那麼IOC便是這個主角進行演出的舞臺,沒有IOC作為Bean的承載,那麼Bean便不會在程式設計中大放異彩。作為Spring核心元件的重要一員,瞭解其內部實現對我們程式設計和窺探Spring內幕是相當有幫助的,下面一步步從原始碼的角度來剖析IOC究竟是怎樣實現的。
介面設計
首先我們先通過一張介面設計圖瞭解下,IOC容器整體的設計架構
原始碼剖析
BeanFacory
通過上面的介面設計圖我們可以看到,在Spring當中最基本的IOC容器介面是BeanFactory,這個介面當中定義作為一個IOC容器最基本的一些操作和功能,下來看看它的原始碼:
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
/**
* BeanFactory作為最原始同時也最重要的Ioc容器,它主要的功能是為依賴注入 (DI) 提供支援,BeanFactory 和相關的介面.這裡定義的只是一系列的介面方法,通過這一系列的BeanFactory介面,可以使用不同的Bean的檢索方法很方便地從Ioc容器中得到需要的Bean,從而忽略具體的Ioc容器的實現,從這個角度上看,這些檢索方法代表的是最為基本的容器入口。
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @since 13 April 2001
*/
public interface BeanFactory {
/**
* 轉定義符"&" 用來引用例項,或把它和工廠產生的Bean區分開,就是說,如果一個FactoryBean的名字為a,那麼,&a會得到那個Factory
*
* FactoryBean和BeanFactory 是在Spring中使用最為頻繁的類,它們在拼寫上很相似。一個是Factory,也就是Ioc容器或物件工廠;一個
* 是Bean。在Spring中,所有的Bean都是由BeanFactory(也就是Ioc容器)來進行管理的。但對FactoryBean而言,這個Bean不是簡單的Be
* an,而是一個能產生或者修飾物件生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似。
*/
String FACTORY_BEAN_PREFIX = "&";
/**
* 五個不同形式的getBean方法,獲取例項
* @param name 檢索所用的Bean名
* @return Object(<T> T) 例項物件
* @throws BeansException 如果Bean不能取得
*/
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
/**
* 讓使用者判斷容器是否含有指定名字的Bean.
* @param name 搜尋所用的Bean名
* @return boolean 是否包含其中
*/
boolean containsBean(String name);
/**
* 查詢指定名字的Bean是否是Singleton型別的Bean.
* 對於Singleton屬性,可以在BeanDefinition指定.
* @param name 搜尋所用的Bean名
* @return boolean 是否包是Singleton
* @throws NoSuchBeanDefinitionException 沒有找到Bean
*/
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
/**
* 查詢指定名字的Bean是否是Prototype型別的。
* 與Singleton屬性一樣,可以在BeanDefinition指定.
* @param name 搜尋所用的Bean名
* @return boolean 是否包是Prototype
* @throws NoSuchBeanDefinitionException 沒有找到Bean
*/
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
/**
* 查詢指定了名字的Bean的Class型別是否是特定的Class型別.
* @param name 搜尋所用的Bean名
* @param typeToMatch 匹配型別
* @return boolean 是否是特定型別
* @throws NoSuchBeanDefinitionException 沒有找到Bean
*/
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
/**
* 查詢指定名字的Bean的Class型別.
* @param name 搜尋所用的Bean名
* @return 指定的Bean或者null(沒有找到合適的Bean)
* @throws NoSuchBeanDefinitionException 沒有找到Bean
*/
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
/**
* 查詢指定了名字的Bean的所有別名,這些都是在BeanDefinition中定義的
* @param name 搜尋所用的Bean名
* @return 指定名字的Bean的所有別名 或者一個空的陣列
*/
String[] getAliases(String name);
}
通過上面的介面說明我們看到在BeanFactory裡面只對IOC容器最基本的行為做了定義,而不關心Bean是怎樣定義和載入的。如果我們想要知道一個工廠具體生產物件的過程,就需要去看這個介面的實現類,在Spring當中實現這個介面的有很多子類,下面我們來看看其一個常用的實現類XmlBeanFacotry的程式碼吧。
XmlBeanFactory
package org.springframework.beans.factory.xml;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.io.Resource;
/**
* XmlBeanFactory是BeanFactory的最簡單實現類
*
* XmlBeanFactory的功能是建立在DefaultListableBeanFactory這個基本容器的基礎上的,並在這個基本容器的基礎上實行了其他諸如
* XML讀取的附加功能。XmlBeanFactory使用了DefaultListableBeanFactory作為基礎類,DefaultListableBeanFactory是一個很重
* 要的Ioc實現,會在下一章進行重點論述。
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @since 15 April 2001
*/
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
* 根據給定來源,建立一個XmlBeanFactory
* @param resource Spring中對與外部資源的抽象,最常見的是對檔案的抽象,特別是XML檔案。而且Resource裡面通常
* 是儲存了Spring使用者的Bean定義,比如applicationContext.xml在被載入時,就會被抽象為Resource來處理。
* @throws BeansException 載入或者解析中發生錯誤
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
* 根據給定來源和BeanFactory,建立一個XmlBeanFactory
* @param resource Spring中對與外部資源的抽象,最常見的是對檔案的抽象,特別是XML檔案。而且Resource裡面通常
* 是儲存了Spring使用者的Bean定義,比如applicationContext.xml在被載入時,就會被抽象為Resource來處理。
* @param parentBeanFactory 父類的BeanFactory
* @throws BeansException 載入或者解析中發生錯誤
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
我們可以看到要構造XmlBeanFactory物件的話需要一個Resource物件,這個Resource是什麼呢?
簡單的講Resource介面是為了提供更強的訪問底層資源能力的抽象,它是spring訪問資源最基本的介面。
通過XmlBeanFactory我們也可以大致可以猜到,對於該類的要傳的Resource物件便是一個xml的檔案流。那麼,這下我們知道這個BeanFactory的簡單實現類的用法,也知道了構造這個物件的的引數,先動手實踐建立一個最原始的Ioc容器吧。
利用XmlBeanFactory實現最原始Ioc容器
準備工作
1、實體類User.java
public class User{
private String name;
private int age;
getter();
setter();
}
2、XML資原始檔beans.xml
<bean id="user1" name="user1" class="com.yanxiao.ioc.User">
<property name="name" value="yanxiao"></property>
<property name="age" value="20"></property>
</bean>
最原始IOC容器的使用
package com.yanxiao.ioc;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
/**
* Created by yanxiao on 2016/7/28.
*/
@SuppressWarnings("deprecation")
public class MySimpleBeanFactory {
public static void main(String[] args) {
ClassPathResource resource = new ClassPathResource("META-INF/beans.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
User user = beanFactory.getBean("user1", User.class);
System.out.println(user.getName()+" : "+user.getAge());
}
}
跟蹤上面的程式碼流程
使用單步除錯瞭解上面程式碼的主要內部呼叫流程,這裡沒有跟蹤getBean()方法的過程,該步驟後設計Bean的解析過程,後面我們再說這個
呼叫順序 | 類名 | 方法名 |
---|---|---|
1 | XmlBeanFactory | 構造方法(Resource) |
2 | DefaultListableBeanFactory | 構造方法(BeanFactory) |
3 | AbstractAutowireCapableBeanFactory | 構造方法(BeanFactory) |
4 | AbstractAutowireCapableBeanFactory | setParentFactory(BeanFactory) |
5 | XmlBeanDefinitionReader | loadBeanDefinitions(Resource) |
6 | AbstractBeanDefinitionReader | getResourceLoader() |
通過上面的流程,我們可以簡單的將使用IOC容器的步驟概括如下:
1.建立Ioc配置檔案的抽象資源,這個抽象資源包含了BeanDefinition的定義資訊
2.建立一個BeanFactory,這裡使用了DefaultListableBeanFactory
3.建立一個載入BeanDefinition的讀取器,這裡使用XmlBeanDefinitionReader來載入XML檔案形式的BeanDefinition
4.然後將上面定位好的Resource,通過一個回撥配置給BeanFactory
5.從定位好的資源位置讀入配置資訊,具體的解析過程由XmlBeanDefinitionReader完成
6.完成整個載入和註冊Bean定義之後,需要的Ioc容器就初步建立起來了
我們對這個內部流程有了瞭解了以後,下面可以在實踐當中進行應用,我們之前寫的MySimpleBeanFactory程式碼便可以更改為下面的形式:
public static void main(String[] args) {
ClassPathResource resource = new ClassPathResource("META-INF/beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
User user = factory.getBean("user", User.class);
System.out.println(user.getName()+" : "+user.getAge());
}
兩者的作用是等效的。
我們現在對IOC大體上有了初步瞭解後,下面對一些關鍵點進行細節上的一些分析。首先,從我們第一步建立的ClassPathResource物件說起,重新回過頭來認識Spring當中的Resource。
Resource介面體系
在上面的文章我們也簡單提到過 Resource 介面的作用,其實Spring使用自己的抽象結構對整個框架中所用到資源進行統一的封裝,封裝這個介面的原因其實大家也大致可以想到,我們在程式設計當中所遇到的資原始檔型別多種多樣有File、InputStream、UrlResource、ContextResource等等,面對如此多的資源型別框架內部要是不設計出一套成熟合理的資源封裝體系的話,無疑對框架的實現和開發者的程式設計來說造成很多的複雜性。面向物件程式設計當中的封裝和繼承特性,在Spring當中可謂應用的淋漓盡致,有時間自己可以花點功夫體會Spring框架這一套設計體系對我們的以後編寫可擴充套件和可用程式碼有很大的好處。
繼續展開上面Resource介面的介紹,該介面作為資源的一個原始封裝,它繼承自InputStreamResource介面,InputStreamResource只有InputStream getInputStream()
這樣一個方法,通過這次繼承賦予了通過Resource物件獲得InputStram流的功能。下面看看介面的原始碼:
InputStreamResource介面
package org.springframework.core.io;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Juergen Hoeller
* @since 20.01.2004
*/
public interface InputStreamSource {
/**
* 返回InputStream的類,比如File、Classpath下的資源和Byte Array等
* @return InputStream 返回一個新的InputStream的物件
* @throws IOException
*/
InputStream getInputStream() throws IOException;
}
Resource介面
package org.springframework.core.io;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
/**
* Resource介面抽象了所有Spring內部使用到的底層資源:File、URL、Classpath等。
* 同時,對於來源不同的資原始檔,Resource也有不同實現:檔案(FileSystemResource)、Classpath資源(ClassPathResource)、
* URL資源(UrlResource)、InputStream資源(InputStreamResource)、Byte陣列(ByteArrayResource)等等。
*
* @author Juergen Hoeller
* @since 28.12.2003
*/
public interface Resource extends InputStreamSource {
/**
* 判斷資源是否存在
* @return boolean 是否存在
*/
boolean exists();
/**
* 判斷資源是否可讀
* @return boolean 是否可讀
*/
boolean isReadable();
/**
* 是否處於開啟狀態
* @return boolean 是否開啟
*/
boolean isOpen();
/**
* 得到URL型別資源,用於資源轉換
* @return URL 得到URL型別
* @throws IOException 如果資源不能開啟則丟擲異常
*/
URL getURL() throws IOException;
/**
* 得到URI型別資源,用於資源轉換
* @return URI 得到URI型別
* @throws IOException 如果資源不能開啟則丟擲異常
*/
URI getURI() throws IOException;
/**
* 得到File型別資源,用於資源轉換
* @return File 得到File型別
* @throws IOException 如果資源不能開啟則丟擲異常
*/
File getFile() throws IOException;
/**
* 獲取資源長度
* @return long 資源長度
* @throws IOException 如果資源不能開啟則丟擲異常
*/
long contentLength() throws IOException;
/**
* 獲取lastModified屬性
* @return long 獲取lastModified
* @throws IOException 如果資源不能開啟則丟擲異常
*/
long lastModified() throws IOException;
/**
* 建立一個相對的資源方法
* @param relativePath 相對路徑
* @return Resource 返回一個新的資源
* @throws IOException 如果資源不能開啟則丟擲異常
*/
Resource createRelative(String relativePath) throws IOException;
/**
* 獲取檔名稱
* @return String 檔名稱或者null
*/
String getFilename();
/**
* 得到錯誤處理資訊,主要用於錯誤處理的資訊列印
* @return String 錯誤資源資訊
*/
String getDescription();
}
ClassPathResource
再看看我們上面程式碼當中所用到的Resource介面的實現類ClassPathResource的部分需要重點關注的原始碼
package org.springframework.core.io;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* @author Juergen Hoeller
* @author Sam Brannen
* @since 28.12.2003
* @see ClassLoader#getResourceAsStream(String)
* @see Class#getResourceAsStream(String)
*/
public class ClassPathResource extends AbstractFileResolvingResource {
private final String path;
private ClassLoader classLoader;
private Class<?> clazz;
/**
通過資源的路徑建立一個ClassPathResource物件
*/
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
/**
上面構造方法的具體實現
*/
public ClassPathResource(String path, ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
//去掉路徑最前面的 /
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
/**
* Create a new {@code ClassPathResource} for {@code Class} usage.
* The path can be relative to the given class, or absolute within
* the classpath via a leading slash.
* @param path relative or absolute path within the class path
* @param clazz the class to load resources with
* @see java.lang.Class#getResourceAsStream
*/
public ClassPathResource(String path, Class<?> clazz) {
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.clazz = clazz;
}
/**
獲得InputStram的具體實現
* This implementation opens an InputStream for the given class path resource.
* @see java.lang.ClassLoader#getResourceAsStream(String)
* @see java.lang.Class#getResourceAsStream(String)
*/
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
/**
獲得檔名
* This implementation returns the name of the file that this class path
* resource refers to.
* @see org.springframework.util.StringUtils#getFilename(String)
*/
@Override
public String getFilename() {
return StringUtils.getFilename(this.path);
}
}
DefaultListableBeanFactory類
我們已經把資源層面上的介面和實現瞭解了,下面分析我們之前程式碼當中的DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
這句。在這裡我們有引入了一個新類DefaultListableBeanFactory,從IOC類圖中可以看到這個是BeanFactory的一個預設實現類,其例項物件可以作為一個獨立使用的IOC容器,可以認為該物件是容納Bean物件的一個容器,我們定義好的Bean物件經過Spring的載入和載入等處理後,最終交由改工廠進行管理,我們需要得到Bean物件時便可以向它獲取。
我們呼叫其無參構造方法建立了factory物件,看看底層都做了哪些事情。
@SuppressWarnings("serial")
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
/**
* Create a new DefaultListableBeanFactory.
*/
public DefaultListableBeanFactory() {
super();//呼叫了父類的無參構造方法
}
}
接下來跟蹤父類AbstractAutowireCapableBeanFactory中原始碼
AbstractAutowireCapableBeanFactory類
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
/**
* Create a new AbstractAutowireCapableBeanFactory.
*/
public AbstractAutowireCapableBeanFactory() {
super();//父類AbstractBeanFactory的無參構造方法,程式碼在下面↓↓↓
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
}
AbstractBeanFactory類
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
/**
* Create a new AbstractBeanFactory.
*/
public AbstractBeanFactory() {
//然而這裡並沒有做什麼
}
}
BeanDefinition的載入、解析和註冊
上面我們建立好了 DefaultListableBeanFactory 物件,已經有了容納Bean的容器了,但我們通過剛才分析原始碼可以看到現在我們還沒有對該factory進行其他實用性的操作,裡面還沒有任何東西。那麼,我們定義在配置檔案定義的Bean是如何進入Spring容器當中的,就得繼續分析下面的程式碼了。我們先分析XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
的實現。
從XmlBeanDefinitionReader類名中我們又見到一個新名詞BeanDefinition,我們先了解下Spring當中BeanDefinition的作用。
BeanDefinition類似於Resource介面的功能,起到的作用就是對所有的Bean進行一層抽象的統一,把形式各樣的物件統一封裝成一個便於Spring內部進行協調管理和排程的資料結構,BeanDefinition遮蔽了不同物件對於Spring框架的差異。
接著我們看XmlBeanDefinitionReader類中關鍵部分的原始碼
XmlBeanDefinitionReader類
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//對Resource引數首先做一層封裝處理,主要是為了獲得getReader()這個方法的實現,原始碼在下面↓↓↓↓
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
// 呼叫DefaultResourceLoader的getResources方法完成具體的Resource定位
try {
//從EncodedResource中獲取已經封裝的Resource物件並再次從Resource中獲取inputStream
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();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
/**
* 1.獲取XML檔案的驗證模式,為正確載入XML做好準備
* 2.從定位好的資源位置處載入XML,對XML進行解析獲得Document物件
* 3.返回的Document,在工廠中註冊Bean
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 對XML檔案進行解析獲得Document物件, 這個解析過程由DefaultDocumentLoader完成
Document doc = doLoadDocument(inputSource, resource);
// 啟動對BeanDefinition解析的詳細過程, 解析過程中會使用到Spring的Bean配置規則
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);
}
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
//loadDocument直接用於註冊Document,getValidationModeForResource方法作用於XML的載入
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
EncodedResource類
//Resource的封裝正是為了使用該getReader方法的功能
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());
}
}
XML的驗證
上面的原始碼涉及到了對XML載入的過程,而對於一個XML的載入可以將其分為三步
1.讀取XML檔案的驗證模式,即判讀其使用的是DTD還是XSD
2.依據上面的驗證模式對XML檔案進行驗證
3.載入XML檔案
上面的過程在原始碼的體現如下:
XmlBeanDefinitionReader類
public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;
public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//如果手動指定了驗證模式則使用指定的驗證模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//如果沒有指定,則交由Spring進行判斷
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
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); //自動檢測在detectValidationMode完成
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}
XmlValidationModeDetector類
//對XML的驗證模式進行列舉
public static final int VALIDATION_NONE = 0;
public static final int VALIDATION_AUTO = 1;
public static final int VALIDATION_DTD = 2;
public static final int VALIDATION_XSD = 3;
private static final String DOCTYPE = "DOCTYPE";
//XML驗證模式的自動檢測實現方法
public int detectValidationMode(InputStream inputStream) throws IOException {
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)) {
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
private boolean hasDoctype(String content) {
return content.contains(DOCTYPE);
}
private boolean hasOpeningTag(String content) {
if (this.inComment) {
return false;
}
int openTagIndex = content.indexOf('<');
return (openTagIndex > -1 && (content.length() > openTagIndex + 1) &&
Character.isLetter(content.charAt(openTagIndex + 1)));
}
解析XML獲得Document物件
通過以上的驗證準備,就可以對XML檔案進行載入了。而載入的過程XmlBeanDefinitionReader並沒有自己去完成,而是由DocumentLoader介面去完成的。跟蹤原始碼我們發現具體的實現是DefaultDocumentLoader.loadDocument()
,原始碼體現如下
XmlBeanDefinitionReader類
//自己並沒有實現loadDocment的過程,而是呼叫documentLoader成員的loadDocument
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
//documentLoader的型別為DefaultDocumentLoader
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
接著看DefaultDocumentLoader中的方法實現
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);
}
通過這個方法便可以獲得XML的Document物件了。
獲得BeanDefinition物件
現在我們已經獲得XML解析後的Document物件,接下來只要在對該Document結構進行分析便可以知道Bean在XML中是如何定義的,也就能將其轉換為BeanDefinition物件。
這個過程呼叫的是XmlBeanDefinitionReader類的registerBeanDefinitions(Document doc, Resource resource)
方法,程式碼如下:
XmlBeanDefinitionReader類
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 具體的解析過程在BeanDefinitionDocumentReader的registerBeanDefinitions方法中完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
DefaultBeanDefinitionDocumentReader類
public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;
public static final String NESTED_BEANS_ELEMENT = "beans";
public static final String ALIAS_ELEMENT = "alias";
public static final String ALIAS_ATTRIBUTE = "alias";
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement(); // 獲得Document的根元素
doRegisterBeanDefinitions(root);
}
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)) {
return;
}
}
}
preProcessXml(root);
//這裡我們看到XmlBeanDefinitionReader對具體元素屬性的解析是通過呼叫parseBeanDefinitions完成的
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
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)) { // 解析import元素
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 解析alias元素
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // 解析bean元素
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // 解析內嵌beans元素, 作為根節點遞迴解析
doRegisterBeanDefinitions(ele);
}
}
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 具體的解析委託給BeanDefinitionParserDelegate來完成
// BeanDefinitionHolder是BeanDefinition的封裝類, 封裝了BeanDefinition、Bean的名字和別名, 用它來完成向IoC容器註冊.
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
看了上面的程式碼發現其實最後解析的重任交給了processBeanDefinition這