Spring IOC容器初始化程序(二)配置資訊加載與注冊
上文中 Spring IOC容器初始化程序(一)資源定位程序 我們一起分析了資源定位的程序,Spring IOC 容器在初始化的程序中并不是直接讀取組態檔然后進行加載,而是在開始做了許多準備作業,先初始化資源讀取器,設定其讀取策略,校驗策略,再將資料加載至記憶體中,最后封裝為BeanDefinition物件進行注冊,
整體加載的時序圖

工程目錄

POM檔案
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springTest</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
</project>
測驗代碼
public class ServiceB {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Object serviceA = context.getBean("serviceA");
System.out.println(serviceA);
}
}
xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<context:component-scan base-package="com.donkeys.spring"/>
<bean id="serviceA" class="com.donkeys.spring.service.ServiceA"></bean>
</beans>
加載程序
接上文,上文中我們已經獲取到了ClassPathXmlApplicationContext的實體化物件回傳的組態檔路徑資訊,同時呼叫了資源讀取器的loadBeanDefinitions方法,下面從這個方法開始,正式進入到資源加載程序
XmlBeanDefinitionReader 的loadBeanDefinitions方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException;
/**
* Load bean definitions from the specified XML file.
* @param encodedResource the resource descriptor for the XML file,
* allowing to specify an encoding to use for parsing the file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
//判斷傳入的xml檔案資源是否為空
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//resourcesCurrentlyBeingLoaded 是一個ThreadLocal 變數,從當前執行緒變數獲取當前的資源描述檔案
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 {
//將資源描述檔案轉換為 輸入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//開始真正執行讀取Bean定義的代碼
//從輸入流中讀取Spring Bean的定義
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();
}
}
}
這個方法中做了2件事
- 判斷當前資源描述檔案是否重復加載
- 如果沒有重復加載,則將當前資源描述檔案,轉換為一個輸入流,讓reader進行讀取
真正讀取的方法是在 doLoadBeanDefinitions 中執行的
XmlBeanDefinitionReader 的doLoadBeanDefinitions 方法
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException ;
/**
* Actually load bean definitions from the specified XML file.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
* @see #doLoadDocument
* @see #registerBeanDefinitions
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//doLoadDocument方法,將輸入流轉換為一個檔案流,同時這個方法內部,還對資源進行了校驗
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);
}
}
該方法中,將輸入流轉換為了一個檔案流,最后呼叫了注冊方法,我們可以先看一下doLoadDocument 方法,
/**
* Actually load the specified document using the configured DocumentLoader.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the DOM Document
* @throws Exception when thrown from the DocumentLoader
* @see #setDocumentLoader
* @see DocumentLoader#loadDocument
*/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
可以看到,這個方法的回傳值直接呼叫了loadDocument方法,同時,還做了2件事,
- 先獲取了物體決議器
- 然后獲取了資源檔案的校驗方式,真正的校驗作業是在后面進行的,我們繼續往下看
XmlBeanDefinitionReader 的成員變數 documentLoader 的類為 DefaultDocumentLoader ,所以我們直接看 DefaultDocumentLoader 的 loadDocument 方法
/**
* Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
* XML parser.
*/
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//靜態工廠創建一個DocumentBuilderFactory物件,同時這個方法還會根據validationMode生成xml檔案校驗規則
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
//生成DocumentBuilder物件,用于決議傳入的檔案流
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
//決議檔案流,回傳檔案物件,決議程序中進行校驗
return builder.parse(inputSource);
}
到這里,檔案被決議為了檔案物件,組態檔已經完全加載到了記憶體中,剩下的就是決議,注冊就完成了容器的初始化作業
下面回到XmlBeanDefinitionReader 的doLoadBeanDefinitions 方法,進入到注冊程序
注冊程序
準備進入registerBeanDefinitions 方法中
XmlBeanDefinitionReader 的registerBeanDefinitions方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException;
/**
* Register the bean definitions contained in the given DOM document.
* Called by {@code loadBeanDefinitions}.
* <p>Creates a new instance of the parser class and invokes
* {@code registerBeanDefinitions} on it.
* @param doc the DOM document
* @param resource the resource descriptor (for context information)
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of parsing errors
* @see #loadBeanDefinitions
* @see #setDocumentReaderClass
* @see BeanDefinitionDocumentReader#registerBeanDefinitions
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//創建bean定義檔案讀取器用于讀取給定DOM檔案中包含的bean定義,
//可以看到Spring 中分工是很明確的,剛剛讀取檔案的類叫做 XmlBeanDefinitionReader,他把xml檔案轉換為了檔案物件
//這里讀取檔案中bean定義的就是BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//注冊前beanDefinition 的數量
int countBefore = getRegistry().getBeanDefinitionCount();
//決議并注冊
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//注冊后beanDefinition 的數量差
return getRegistry().getBeanDefinitionCount() - countBefore;
}
DefaultBeanDefinitionDocumentReader類
/**
* This implementation parses bean definitions according to the "spring-beans" XSD
* (or DTD, historically).
* <p>Opens a DOM Document; then initializes the default settings
* specified at the {@code <beans/>} level; then parses the contained bean definitions.
*/
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//獲取檔案根物件
Element root = doc.getDocumentElement();
//開始執行注冊方法
doRegisterBeanDefinitions(root);
}
/**
* Register each bean definition within the given root {@code <beans/>} element.
*/
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
/**
* 生成一個BeanDefinitionParserDelegate 物件
* 該類中定義了關于 spring 的xml組態檔的關鍵字常量配置
* 這里又看到了分工明確的類,生成一個BeanDefinitionParserDelegate物件就是用于決議節點中Bean的定義資訊的
*/
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;
}
}
}
//在決議Bean 定義之前,進行自定義的決議,增強決議程序的可擴展性
//留給子類實作
/**
* 模板設計模式,
* 如果繼承自DefaultBeanDefinitionDocumentReader的子類需要在Bean決議前后做一些處理的話,
* 那么只需要重寫這兩個方法就可以了
*/
preProcessXml(root);
//從Document的根元素進行Bean定義的Document物件
//從根元素開始對檔案進行決議,決議程序是由delegate物件完成,生成一個BeanDefinitionHolder物件,包含組態檔中所有的配置資訊
//Spring 的組態檔中,不止有bean 關鍵字,還有Import,Alias等關鍵,這里我們只討論bean的決議
parseBeanDefinitions(root, this.delegate);
//決議后處理,由子類實作
postProcessXml(root);
this.delegate = parent;
}
可以看到DefaultBeanDefinitionDocumentReader的 doRegisterBeanDefinitions 方法中做了3件事
- 生成了一個用于決議檔案配置的代表BeanDefinitionParserDelegate物件
- 配置了2個模板方法分別提供給用戶用于在加載beanDefinition 之前和之后做操作
- 真正的決議方法在parseBeanDefinitions中執行
下面進入parseBeanDefinitions方法中
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
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) {
//如果是import 節點,則進行匯入決議
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//如果元素節點是Alias 別名元素,則進行別名決議
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//元素節點既不是匯入元素,也不是別名元素,那就是普通的bean元素
//按照Spring 的Bean 規則決議元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
//對beans 標簽進行處理
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
/**
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//通過委托物件,決議當前元素,回傳一個BeanDefinitionHolder物件,該物件中會包含我們在組態檔中給于的屬性
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//這里會判斷是否該元素下有自定義的元素,如果有則將其加載進來
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//對bdHolder進行注冊,
//委托設計模式
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
//在容器背景關系中,發出現回應事件,通知相關的監聽器,這個bean的相關配置已經加載完了
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
這里列出了三段代碼,
第一段代碼:
? 從檔案根節點開始向下遍歷,依次判斷該檔案節點元素是默認元素還是用戶自定義元素,如果是自定義元素,則進行自定義元素決議,否則按照默認元素進行決議
第二段代碼:
? 這段代碼是默認元素的執行的邏輯,這里對默認元素再次進行了分類,分別分為import 標簽,alias標簽,bean 標簽,beans標簽
我們主要討論bean標簽,這幾個方法都比較類似
第三段代碼:
? 這段代碼主要描述了bean標簽的決議程序,首先delegate根據傳入的節點元素生成了BeanDefinitionHolder物件,這個物件包含了這個bean的所有配置資訊,然后開始加載自定義元素,自定義元素也需要用戶提供方法去決議,
最后呼叫了BeanDefinitionReaderUtils工具類的靜態方法registerBeanDefinition,在容器中注冊了這個BeanDefinitionHolder物件
下面我們繼續看BeanDefinitionReaderUtils的registerBeanDefinition方法
BeanDefinitionReaderUtils的靜態方法registerBeanDefinition
/**
* Register the given bean definition with the given bean factory.
* @param definitionHolder the bean definition including name and aliases
* @param registry the bean factory to register with
* @throws BeanDefinitionStoreException if registration failed
*/
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
//獲取當前definitionHolder的名稱,這個名稱可以用戶定義,如果用戶沒有定義,則spring會自動為其生成一個name,
//name 的生成規則是 首先獲取這個類的類名
//然后判斷是否是內部bean,如果是,則為 類名#hash值
//不是內部bean ,則判斷當前容器是否有同名的,沒有,則為: 類名
//有同名,設定了一個計數器,如果當前有2個同名,那么該計數器最后值為3 則最終名稱為 類名#3
//這個取名程序是在生成這個BeanDefinitionHolder物件時做的
String beanName = definitionHolder.getBeanName();
//開始進行注冊
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
BeanDefinitionRegistry介面的registerBeanDefinition方法
BeanDefinitionRegistry是一個介面,這里的主要實作類是DefaultListableBeanFactory類
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
//判斷beanName名是否為空
Assert.hasText(beanName, "Bean name must not be empty");
//判斷beanDefinition物件是否為空
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
//注冊前的最后一次校驗作業,這里的校驗不是校驗XML檔案
//這里主要對AbstractBeanDefinition 屬性的methodOverrides校驗
//校驗methodOverrides是否與工廠方法并存
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition oldBeanDefinition;
//處理已經注冊的beanName 的情況,
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
//如果該beanName已經注冊,且在配置中配置了bean不允許被覆寫,則拋出例外
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
//所有處理完成,將該beanDefinition加入Map快取
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
在進行為空校驗之后,分別處理了2種情況
- 容器中已經有這個beanName物件存在
- 這里會獲取配置,是否允許覆寫,如果不允許覆寫,則會拋出例外
- 容器中沒有這個beanName物件存在
這兩個方法的正常流程走完都是向beanDefinitionMap中加入該bean的定義, 至此,Spring IOC容器的加載程序全部結束,組態檔中的所有bean 定義都被加入了beanDefinitionMap中
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/271315.html
標籤:java
上一篇:Java實作學生資訊管理系統
下一篇:記一次CPU飆升的問題排查
