該系列文章是本人在學習 Spring 的程序中總結下來的,里面涉及到相關原始碼,可能對讀者不太友好,請結合我的原始碼注釋 Spring 原始碼分析 GitHub 地址 進行閱讀
Spring 版本:5.1.14.RELEASE
開始閱讀這一系列文章之前,建議先查看《深入了解 Spring IoC(面試題)》這一篇文章
該系列其他文章請查看:《死磕 Spring 之 IoC 篇 - 文章導讀》
BeanDefinition 的加載階段(XML 檔案)
上一篇文章 《Bean 的“前身”》 對 BeanDefinition 進行了介紹,Bean 是根據 BeanDefinition 配置元資訊物件生成的,我們在 Spring 中通常以這兩種方式定義一個 Bean:面向資源(XML、Properties)、面向注解,那么 Spring 是如何將這兩種方式定義的資訊轉換成 BeanDefinition 物件的,接下來會先分析面向資源(XML、Properties)這種方式 Spring 是如何處理的
下來熟悉一段代碼:
dependency-lookup-context.xml:
<?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" >
<property name="id" value="https://www.cnblogs.com/lifullmoon/p/1"/>
<property name="name" value="https://www.cnblogs.com/lifullmoon/p/小馬哥"/>
</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 容器的使用程序大致如下:
- 創建 BeanFactory 物件(底層 IoC 容器)
- 創建 BeanDefinitionReader 物件(資源決議器),關聯第
1步創建的 BeanFactory - 通過 BeanDefinitionReader 加載 XML 組態黨澩,決議出所有的 BeanDefinition 物件
- 進行依賴查找
上面的第 3 步會決議 Resource 資源,將 XML 檔案中定義的 Bean 決議成 BeanDefinition 配置元資訊物件,并往 BeanDefinitionRegistry 注冊中心注冊,此時并沒有生成對應的 Bean 物件,需要通過依賴查找獲取到 Bean,當然,我們在實際場景中一般不會這樣使用 Spring,這些作業都會有 Spring 來完成,接下來我們一起來看看 Sping 是如何加載 XML 檔案的
BeanDefinitionReader 體系結構
org.springframework.beans.factory.support.BeanDefinitionReader 介面的類圖如下所示:
總覽:
-
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 物件,目的是讓資源物件可設定編碼
- 獲取當前執行緒正在加載的 Resource 資源集合,添加當前 Resource,防止重復加載
- 從 Resource 資源獲取 InputStream 流物件(支持編碼)
- 【核心】呼叫
doLoadBeanDefinitions(InputSource inputSource, Resource resource)方法,執行加載 Resource 資源程序,決議出 BeanDefinition 進行注冊 - 從當前執行緒移除當前加載的 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 各種例外
}
- 呼叫
doLoadDocument(InputSource inputSource, Resource resource)方法,獲取 XML Document 實體 - 呼叫
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 檔案的正確性
}
- 獲取
org.xml.sax.EntityResolver物體決議器,ResourceEntityResolver,根據 publicId 和 systemId 獲取對應的 DTD 或 XSD 檔案,用于對 XML 檔案進行驗證,這個類比較關鍵,在后續文章會講到 - 獲取 XML 檔案驗證模式,保證 XML 檔案的正確性,通常情況下都是 XSD 模式
- 獲取指定的驗證模式,如果手動指定,則直接回傳,通常情況下不會
- 從 Resource 資源中獲取驗證模式,根據 XML 檔案的內容進行獲取,如果包含
DOCTYPE內容則為 DTD 模式,否則為 XSD 模式 - 如果還沒有獲取到驗證模式,則默認為 XSD 模式
- 通過 DefaultDocumentLoader 根據 Resource 獲取一個 Document 物件
- 創建 DocumentBuilderFactory 物件
factory,開啟校驗 - 根據
factory創建 DocumentBuilder 物件builder,設定 EntityResolver(第1步創建的)、ErrorHandler 屬性 - 通過
builder對inputSource(Resource 資源)進行決議,回傳一個 Document 物件
- 創建 DocumentBuilderFactory 物件
上述程序目的就是獲取到 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;
}
- 創建 DefaultBeanDefinitionDocumentReader 物件
documentReader - 獲取已注冊的 BeanDefinition 數量
- 創建 XmlReaderContext 物件(讀取 Resource 資源的背景關系物件),注意這里會初始化一個 DefaultNamespaceHandlerResolver 物件,用于處理自定義標簽(XML 檔案),比較關鍵,在后續文章會講到
- 根據 Document、XmlReaderContext 決議出所有的 BeanDefinition 并注冊,呼叫
DefaultBeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法 - 計算新注冊的 BeanDefinition 數量并回傳
拓展:DTD 與 XSD 的區別?
DTD(Document Type Definition),即檔案型別定義,為 XML 檔案的驗證機制,屬于 XML 檔案中組成的一部分,DTD 是一種保證 XML 檔案格式正確的有效驗證方式,它定義了相關 XML 檔案的元素、屬性、排列方式、元素的內容型別以及元素的層次結構,其實 DTD 就相當于 XML 中的 “詞匯”和“語法”,我們可以通過比較 XML 檔案和 DTD 檔案 來看檔案是否符合規范,元素和標簽使用是否正確,
DTD 在一定的階段推動了 XML 的發展,但是它本身存在著一些缺陷:
- 它沒有使用 XML 格式,而是自己定義了一套格式,相對決議器的重用性較差;而且 DTD 的構建和訪問沒有標準的編程介面,導致決議器很難簡單的決議 DTD 檔案
- DTD 對元素的型別限制較少;同時其他的約束力也比較弱
- DTD 擴展能力較差
- 基于正則運算式的 DTD 檔案的描述能力有限
XSD(XML Schemas Definition),即 XML Schema 語言,針對 DTD 的缺陷由 W3C 在 2001 年推出,XML Schema 本身就是一個 XML 檔案,使用的是 XML 語法,因此可以很方便的決議 XSD 檔案,相對于 DTD,XSD 具有如下優勢:
- XML Schema 基于 XML,沒有專門的語法
- XML Schema 可以像其他 XML 檔案一樣決議和處理
- XML Schema 比 DTD 提供了更豐富的資料型別
- XML Schema 提供可擴充的資料模型
- XML Schema 支持綜合命名空間
- XML Schema 支持屬性組
總結
我們在 Spring 中通常以這兩種方式定義一個 Bean:面向資源(XML、Properties)、面向注解,對于第一種方式如果定義的是一個 XML 檔案,Spring 會通過 XmlBeanDefinitionReader 加載該 XML 檔案,獲取該 Resource 資源的 org.w3c.dom.Document 物件,這個程序會經過校驗、決議兩個步驟
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/262782.html
標籤:Java
上一篇:電影院售票管理系統
