死磕Spring之IoC篇 - BeanDefinition 的加載階段(XML 文件)

該系列文章是本人在學習 Spring 的過程當中總結下來的,裏面涉及到相關源碼,可能對讀者不太友好,請結合個人源碼註釋 Spring 源碼分析 GitHub 地址 進行閱讀html

Spring 版本:5.1.14.RELEASEjava

開始閱讀這一系列文章以前,建議先查看《深刻了解 Spring IoC(面試題)》這一篇文章git

該系列其餘文章請查看:《死磕 Spring 之 IoC 篇 - 文章導讀》github

BeanDefinition 的加載階段(XML 文件)

上一篇文章 《Bean 的「前身」》 對 BeanDefinition 進行了介紹,Bean 是根據 BeanDefinition 配置元信息對象生成的。咱們在 Spring 中一般以這兩種方式定義一個 Bean:面向資源(XML、Properties)面向註解,那麼 Spring 是如何將這兩種方式定義的信息轉換成 BeanDefinition 對象的,接下來會先分析面向資源(XML、Properties)這種方式 Spring 是如何處理的面試

下來熟悉一段代碼:正則表達式

dependency-lookup-context.xmlspring

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
	<!-- <context:component-scan base-package="org.geekbang.thinking.in.spring.ioc.overview" /> -->

    <bean id="user" class="org.geekbang.thinking.in.spring.ioc.overview.domain.User">
        <property name="id" value="1"/>
        <property name="name" value="小馬哥"/>
    </bean>
</beans>
// 建立 BeanFactory 容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
// XML 配置文件 ClassPath 路徑
String location = "classpath:/META-INF/dependency-lookup-context.xml";
// 加載配置
int beanDefinitionsCount = reader.loadBeanDefinitions(location);
System.out.println("Bean 定義加載的數量:" + beanDefinitionsCount);
// 依賴查找
System.out.println(beanFactory.getBean("user"));;

這段代碼是 Spring 中編程式使用 IoC 容器,咱們能夠看到 IoC 容器的使用過程大體以下:編程

  1. 建立 BeanFactory 對象(底層 IoC 容器)
  2. 建立 BeanDefinitionReader 對象(資源解析器),關聯第 1 步建立的 BeanFactory
  3. 經過 BeanDefinitionReader 加載 XML 配置文件資源,解析出全部的 BeanDefinition 對象
  4. 進行依賴查找

上面的第 3 步會解析 Resource 資源,將 XML 文件中定義的 Bean 解析成 BeanDefinition 配置元信息對象,並往 BeanDefinitionRegistry 註冊中心註冊,此時並無生成對應的 Bean 對象,須要經過依賴查找獲取到 Bean。固然,咱們在實際場景中通常不會這樣使用 Spring,這些工做都會有 Spring 來完成。接下來咱們一塊兒來看看 Sping 是如何加載 XML 文件的數組

BeanDefinitionReader 體系結構

org.springframework.beans.factory.support.BeanDefinitionReader 接口的類圖以下所示:dom

總覽:

  • org.springframework.beans.factory.support.BeanDefinitionReader 接口,BeanDefinition 讀取器

  • org.springframework.beans.factory.support.AbstractBeanDefinitionReader 抽象類,提供通用的實現,具體的資源加載邏輯在由子類實現

  • org.springframework.beans.factory.xml.XmlBeanDefinitionReader,XML 文件資源解析器,解析出 BeanDefinition 配置元信息對象並註冊

  • org.springframework.beans.factory.support.PropertiesBeanDefinitionReader,Properties 文件資源解析器

BeanDefinitionReader 接口

org.springframework.beans.factory.support.BeanDefinitionReader 接口,BeanDefinition 讀取器,定義了加載資源的方法,代碼以下:

public interface BeanDefinitionReader {

	/** 返回 BeanDefinition 註冊中心 */
	BeanDefinitionRegistry getRegistry();

	/** 返回 Resource 資源加載器,默認爲 PathMatchingResourcePatternResolver */
	@Nullable
	ResourceLoader getResourceLoader();

	/** 返回類加載器 */
	@Nullable
	ClassLoader getBeanClassLoader();

	/** 返回 Bean 的名稱生成器,默認爲 DefaultBeanNameGenerator */
	BeanNameGenerator getBeanNameGenerator();


	/** 從 Resource 資源中加載 BeanDefinition 並返回數量 */
	int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

	int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
    
	int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;

	int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}

AbstractBeanDefinitionReader 抽象類

org.springframework.beans.factory.support.AbstractBeanDefinitionReader 抽象類,實現了 BeanDefinitionReader 和 EnvironmentCapable 接口,代碼以下:

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader, EnvironmentCapable {

	private final BeanDefinitionRegistry registry;

	@Nullable
	private ResourceLoader resourceLoader;

	@Nullable
	private ClassLoader beanClassLoader;

	private Environment environment;

	private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();

	protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;

		// Determine ResourceLoader to use.
		if (this.registry instanceof ResourceLoader) {
			this.resourceLoader = (ResourceLoader) this.registry;
		}
		else {
			this.resourceLoader = new PathMatchingResourcePatternResolver();
		}

		// Inherit Environment if possible
		if (this.registry instanceof EnvironmentCapable) {
			this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
		}
		else {
			this.environment = new StandardEnvironment();
		}
	}

	@Override
	public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
		Assert.notNull(resources, "Resource array must not be null");
		int count = 0;
		for (Resource resource : resources) {
			count += loadBeanDefinitions(resource);
		}
		return count;
	}

	@Override
	public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(location, null);
	}

	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		// 得到 ResourceLoader 對象
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				// 得到 Resource 數組,由於 Pattern 模式匹配下,可能有多個 Resource 。例如說,Ant 風格的 location
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				// 加載 BeanDefinition 們
				int count = loadBeanDefinitions(resources);
				if (actualResources != null) {
					// 添加到 actualResources 中
					Collections.addAll(actualResources, resources);
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
				}
				return count;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// Can only load single resources by absolute URL.
			// 得到 Resource 對象
			Resource resource = resourceLoader.getResource(location);
			// 加載 BeanDefinition 們
			int count = loadBeanDefinitions(resource);
			if (actualResources != null) {
				// 添加到 actualResources 中
				actualResources.add(resource);
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
			}
			return count;
		}
	}

	@Override
	public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
		Assert.notNull(locations, "Location array must not be null");
		int count = 0;
		for (String location : locations) {
			count += loadBeanDefinitions(location);
		}
		return count;
	}
    
    // ... 省略相關代碼
}

在實現的方法中,最終都會調用 int loadBeanDefinitions(Resource resource) 這個方法,該方法在子類中實現

XmlBeanDefinitionReader

org.springframework.beans.factory.xml.XmlBeanDefinitionReader,XML 文件資源解析器,解析出 BeanDefinition 配置元信息對象並註冊

構造函數

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
	/**
	 * 禁用驗證模式
	 */
	public static final int VALIDATION_NONE = XmlValidationModeDetector.VALIDATION_NONE;

	/**
	 * 自動獲取驗證模式
	 */
	public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;

	/**
	 * DTD 驗證模式
	 */
	public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD;

	/**
	 * XSD 驗證模式
	 */
	public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;

	/** Constants instance for this class. */
	private static final Constants constants = new Constants(XmlBeanDefinitionReader.class);

	/**
	 * 驗證模式,默認爲自動模式。
	 */
	private int validationMode = VALIDATION_AUTO;

	private boolean namespaceAware = false;

	private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

	/**
	 * 解析過程當中異常處理器
	 */
	private ProblemReporter problemReporter = new FailFastProblemReporter();

	private ReaderEventListener eventListener = new EmptyReaderEventListener();

	private SourceExtractor sourceExtractor = new NullSourceExtractor();

	@Nullable
	private NamespaceHandlerResolver namespaceHandlerResolver;

	private DocumentLoader documentLoader = new DefaultDocumentLoader();

	@Nullable
	private EntityResolver entityResolver;

	private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);

	/**
	 * XML 驗證模式探測器
	 */
	private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector();

	/**
	 * 當前線程,正在加載的 EncodedResource 集合。
	 */
	private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal<>(
        "XML bean definition resources currently being loaded");

	/**
	 * Create new XmlBeanDefinitionReader for the given bean factory.
	 * @param registry the BeanFactory to load bean definitions into,
	 * in the form of a BeanDefinitionRegistry
	 */
	public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
		super(registry);
	}
}

loadBeanDefinitions 方法

loadBeanDefinitions(Resource resource) 方法,解析 Resource 資源的入口,方法以下:

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}

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

    // <1> 獲取當前線程正在加載的 Resource 資源集合,添加當前 Resource,防止重複加載
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    if (!currentResources.add(encodedResource)) { // 將當前資源加入記錄中。若是已存在,拋出異常,防止循環加載同一資源出現死循環
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    try {
        // <2> 從 Resource 資源獲取 InputStream 流對象(支持編碼)
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            // <3> 【核心】執行加載 Resource 資源過程,解析出 BeanDefinition 進行註冊
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        } finally {
            // 關閉流
            inputStream.close();
        }
    } catch (IOException ex) {
        throw new BeanDefinitionStoreException(
                "IOException parsing XML document from " + encodedResource.getResource(), ex);
    } finally {
        // <4> 從當前線程移除當前加載的 Resource 對象
        currentResources.remove(encodedResource);
        if (currentResources.isEmpty()) {
            this.resourcesCurrentlyBeingLoaded.remove();
        }
    }
}

將 Resource 封裝成 EncodedResource 對象,目的是讓資源對象可設置編碼

  1. 獲取當前線程正在加載的 Resource 資源集合,添加當前 Resource,防止重複加載
  2. 從 Resource 資源獲取 InputStream 流對象(支持編碼)
  3. 【核心】調用 doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法,執行加載 Resource 資源過程,解析出 BeanDefinition 進行註冊
  4. 從當前線程移除當前加載的 Resource 對象

doLoadBeanDefinitions 方法

doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法,執行加載 Resource 資源過程,解析出 BeanDefinition 進行註冊,方法以下:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        // <1> 獲取 XML Document 實例
        Document doc = doLoadDocument(inputSource, resource);
        // <2> 根據 Document 實例,解析出 BeanDefinition 們並註冊,返回註冊數量
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    }
    // 省略 catch 各類異常
}
  1. 調用 doLoadDocument(InputSource inputSource, Resource resource) 方法,獲取 XML Document 實例
  2. 調用 registerBeanDefinitions(Document doc, Resource resource) 方法,根據 Document 實例,解析出 BeanDefinition 們並註冊,返回註冊數量

doLoadDocument 方法

doLoadDocument(InputSource inputSource, Resource resource) 方法,獲取 Resource 資源對應的 XML Document 實例,方法以下:

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    // <3> 經過 DefaultDocumentLoader 根據 Resource 獲取一個 Document 對象
    return this.documentLoader.loadDocument(inputSource,
            getEntityResolver(), // <1> 獲取 `org.xml.sax.EntityResolver` 實體解析器,ResourceEntityResolver
            this.errorHandler,
            getValidationModeForResource(resource), isNamespaceAware()); // <2> 獲取 XML 文件驗證模式,保證 XML 文件的正確性
}
  1. 獲取 org.xml.sax.EntityResolver 實體解析器,ResourceEntityResolver,根據 publicId 和 systemId 獲取對應的 DTD 或 XSD 文件,用於對 XML 文件進行驗證,這個類比較關鍵,在後續文章會講到
  2. 獲取 XML 文件驗證模式,保證 XML 文件的正確性,一般狀況下都是 XSD 模式
    1. 獲取指定的驗證模式,若是手動指定,則直接返回,一般狀況下不會
    2. 從 Resource 資源中獲取驗證模式,根據 XML 文件的內容進行獲取,若是包含 DOCTYPE 內容則爲 DTD 模式,不然爲 XSD 模式
    3. 若是尚未獲取到驗證模式,則默認爲 XSD 模式
  3. 經過 DefaultDocumentLoader 根據 Resource 獲取一個 Document 對象
    1. 建立 DocumentBuilderFactory 對象 factory,開啓校驗
    2. 根據 factory 建立 DocumentBuilder 對象 builder,設置 EntityResolver(第 1 步建立的)、ErrorHandler 屬性
    3. 經過 builderinputSource(Resource 資源)進行解析,返回一個 Document 對象

上述過程目的就是獲取到 Resource 資源對應的 Document 對象,須要通過校驗和解析兩個過程

registerBeanDefinitions 方法

registerBeanDefinitions(Document doc, Resource resource) 方法,根據 Document 實例,解析出 BeanDefinition 們並註冊,返回註冊數量,方法以下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // <1> 建立 BeanDefinitionDocumentReader 對象
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // <2> 獲取已註冊的 BeanDefinition 數量
    int countBefore = getRegistry().getBeanDefinitionCount();
    // <3> 建立 XmlReaderContext 對象(讀取 Resource 資源的上下文對象)
    // <4> 根據 Document、XmlReaderContext 解析出全部的 BeanDefinition 並註冊
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // <5> 計算新註冊的 BeanDefinition 數量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}
  1. 建立 DefaultBeanDefinitionDocumentReader 對象 documentReader
  2. 獲取已註冊的 BeanDefinition 數量
  3. 建立 XmlReaderContext 對象(讀取 Resource 資源的上下文對象),注意這裏會初始化一個 DefaultNamespaceHandlerResolver 對象,用於處理自定義標籤(XML 文件),比較關鍵,在後續文章會講到
  4. 根據 Document、XmlReaderContext 解析出全部的 BeanDefinition 並註冊,調用 DefaultBeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 方法
  5. 計算新註冊的 BeanDefinition 數量並返回

拓展:DTD 與 XSD 的區別?

DTD(Document Type Definition),即文檔類型定義,爲 XML 文件的驗證機制,屬於 XML 文件中組成的一部分。DTD 是一種保證 XML 文檔格式正確的有效驗證方式,它定義了相關 XML 文檔的元素、屬性、排列方式、元素的內容類型以及元素的層次結構。其實 DTD 就至關於 XML 中的 「詞彙」和「語法」,咱們能夠經過比較 XML 文件和 DTD 文件 來看文檔是否符合規範,元素和標籤使用是否正確。

DTD 在必定的階段推進了 XML 的發展,可是它自己存在着一些缺陷

  1. 它沒有使用 XML 格式,而是本身定義了一套格式,相對解析器的重用性較差;並且 DTD 的構建和訪問沒有標準的編程接口,致使解析器很難簡單的解析 DTD 文檔
  2. DTD 對元素的類型限制較少;同時其餘的約束力也比較弱
  3. DTD 擴展能力較差
  4. 基於正則表達式的 DTD 文檔的描述能力有限

XSD(XML Schemas Definition),即 XML Schema 語言,針對 DTD 的缺陷由 W3C 在 2001 年推出。XML Schema 自己就是一個 XML 文檔,使用的是 XML 語法,所以能夠很方便的解析 XSD 文檔。相對於 DTD,XSD 具備以下優點

  1. XML Schema 基於 XML,沒有專門的語法
  2. XML Schema 能夠像其餘 XML 文件同樣解析和處理
  3. XML Schema 比 DTD 提供了更豐富的數據類型
  4. XML Schema 提供可擴充的數據模型
  5. XML Schema 支持綜合命名空間
  6. XML Schema 支持屬性組

總結

咱們在 Spring 中一般以這兩種方式定義一個 Bean:面向資源(XML、Properties)面向註解,對於第一種方式若是定義的是一個 XML 文件,Spring 會經過 XmlBeanDefinitionReader 加載該 XML 文件,獲取該 Resource 資源的 org.w3c.dom.Document 對象,這個過程會通過校驗、解析兩個步驟

相關文章
相關標籤/搜索