Spring 源碼學習(一)容器的基礎結構

`Spring` 是這樣加載 `xml` 配置的

[TOC]php


本篇筆記主要記錄瞭如下內容:java

使用 ClassPathXmlApplicationContext,經過在 xml 註冊一個 bean,跟蹤代碼,瞭解它從配置文件的 <bean> 標籤,加載到 BeanFactory 註冊表 beanDefinitionMap 的詳細過程。node

展現的代碼摘取了一些核心方法,去掉一些默認設置和日誌輸出,還有大多數錯誤異常也去掉了,小夥伴想看詳細代碼,註釋和 demo,能夠下載我上傳的筆記項目📒git

碼雲 Gitee 地址github

Github 地址web

經過閱讀源碼的過程,瞭解設計者的設計思路和從中學習,對 spring 有個基礎的瞭解。spring


基礎結構

一開始先介紹如何在代碼中註冊和使用 bean數據庫

config.xmlexpress

<?xml version="1.0" encoding="UTF-8"?>
<beans>
	<bean id="book" class="domain.SimpleBook"/>
</beans>
複製代碼

定義一個簡單類:設計模式

SimpleBook.java

public class SimpleBook {

	private int id;

	private String name = "Default Name";

}
複製代碼

使用 ClassPathXmlApplicationContextxml 配置文件中獲取 bean

public static void main(String[] args) {
	ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
	SimpleBook book = context.getBean(SimpleBook.class);
	System.out.println(book.getName());
}
複製代碼

正常運行代碼後,控制檯會輸出:

Default Name
複製代碼

一般來講,咱們要使用一個對象,須要經過 new 初始化,分配內存空間等操做進行實例化,但有了 Spring 容器後,咱們能夠將 SimpleBook 交給了 Spring 進行管理,不須要在代碼中進行 new SimpleBook 等操做,經過自動注入(例如 @Autowire 註解),或者像例子中的,獲取上下文對象,而後使用 getBean()方法,能夠方便的獲取對象實例~。


ClassPathXmlApplicationContext

ClassPathXmlApplicationContext 的繼承體系結構圖:

ClassPathXmlApplicationContext

這種結構圖是經過 IDEA 編輯器的 Diagrams 功能展現的,對當前類右鍵選擇,能夠看到繼承體系,繼承了哪些類和引用了哪些接口,方便咱們去了解~

ClassPathXmlApplicationContext 繼承自 AbstractApplicationContext,而 AbstractRefreshableApplicationContextAbstractApplicationContext 的抽象子類,使用的類註冊工廠是 DefaultListableBeanFactory,這個註冊工廠也很重要,後面會有它的介紹。

簡單來講,DefaultListableBeanFactorySpring 註冊及加載 bean 的默認實現,它會將註冊的 bean放入 beanDefinitionMap 進行 key-value 形式存儲。

在圖片的右上角能看到,ResourceLoader 是它的頂層接口,表示這個類實現了資源加載功能。

構造器的代碼:

public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
	super(parent);
	// 註釋 1.1 獲取資源文件
	setConfigLocations(configLocations);
	if (refresh) {
		refresh();
	}
}
複製代碼

構造器

從這行代碼看出,子類構造器調用了父類的構造器:

super(parent)

一直跟蹤代碼,發現從子類開始,沿着父類一直往上調用,直到 AbstractApplicationContext :

public AbstractApplicationContext() {
	this.resourcePatternResolver = getResourcePatternResolver();
}

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
	this();
	setParent(parent);
}
複製代碼
protected ResourcePatternResolver getResourcePatternResolver() {
	return new PathMatchingResourcePatternResolver(this);
}
複製代碼

初始化函數主要用來設定資源匹配的處理器,ResourcePatternResolver 接口定義了將位置模式(例如, ant樣式的路徑模式)解析爲資源對象的策略,具體實現類是 PathMatchingResourcePatternResolver (路徑匹配資源模式解析器,用來解析咱們傳入的路徑 config.xml


設置配置文件路徑

org.springframework.context.support.AbstractRefreshableConfigApplicationContext

public void setConfigLocations(@Nullable String... locations) {
	if (locations != null) {
		Assert.noNullElements(locations, "Config locations must not be null");
		// 註釋 1.2 將配置資源路徑放入 configLocations 數組中
		this.configLocations = new String[locations.length];
		for (int i = 0; i < locations.length; i++) {
			this.configLocations[i] = resolvePath(locations[i]).trim();
		}
	}
	else {
		this.configLocations = null;
	}
}
複製代碼

resolvePath,用途是:解析給定的路徑,用對應的佔位符(placeholder)替換佔位符

例如 new ClassPathXmlApplicationContext("classpath:config.xml");,就須要解析 classpath,變成正確路徑。

protected String resolvePath(String path) {
	return getEnvironment().resolveRequiredPlaceholders(path);
}
複製代碼

咱們有不一樣的運行環境,devtest 或者 prod,這個時候加載的配置文件和屬性應該有所不一樣,這個時候就須要使用到 Environment 來進行區分。

Spring 環境和屬性是由四個部分組成:

  • Environment : 環境,由 ProfilePropertyResolver 組合。
  • Profile : 配置文件,能夠理解爲,容器裏多個配置組別的屬性和 bean,只有激活的 profile,它對應的組別屬性和 bean 纔會被加載
  • PropertySource : 屬性源, 使用 CopyOnWriteArrayList 數組進行屬性對 key-value 形式存儲
  • PropertyResolver :屬性解析器,這個用途就是解析屬性

Environment

首先來看 StandardServletEnvironment 的繼承體系:

StandardServletEnvironment

能夠看到,頂層接口是 PropertyResolver,它是用來解析屬性的,最終解析調用方法的是

PropertyPlaceholderHelper.replacePlaceholders

public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
	Assert.notNull(value, "'value' must not be null");
    // 用返回的值替換格式爲{@code ${name}}的全部佔位符
	return parseStringValue(value, placeholderResolver, null);
}
複製代碼

Profile

經過這個屬性,能夠同時在配置文件中部署兩套配置,用來適用於生產環境和開發環境,這樣能夠方便的進行切換開發、部署環境,經常使用來更換不一樣的數據庫或者配置文件。

demo:(引用自參考資料第四條)

<!-- 測試環境配置文件 -->
<beans profile="test">
    <context:property-placeholder location="classpath:test/*.properties, classpath:common/*.properties" />
</beans>

<!-- 生產環境配置文件 -->
<beans profile="production">
    <context:property-placeholder location="classpath:production/*.properties, classpath:common/*.properties" />
</beans>

<!-- 開發環境配置文件 -->
<beans profile="development">
    <context:property-placeholder location="classpath:dev/*.properties, classpath:common/*.properties" />
</beans>
複製代碼

有兩種方式能夠設置選擇使用哪套配置:

① 在 web.xml 中設置

<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>test</param-value>
</context-param>
複製代碼

② 在代碼啓動時設置

context.getEnvironment().setActiveProfiles("test");
複製代碼

Property

Property 官方註釋描述:

/** * A description of a JavaBeans Property that allows us to avoid a dependency on * {@code java.beans.PropertyDescriptor}. The {@code java.beans} package * is not available in a number of environments (e.g. Android, Java ME), so this is * desirable for portability of Spring's core conversion facility. * **/

它容許咱們避免對 {@code java.bean . propertydescriptor}的依賴。

由於 {@code java。bean} package 在許多環境中都不可用(例如 Android、Java ME),所以這對於 Spring 的核心轉換工具的可移植性來講是很是理想的。
複製代碼

AbstractEnvironment.java 中能找到,在設置環境 env 時,new 了一個 MutablePropertySources,用這個對象來保存屬性 :

private final MutablePropertySources propertySources = new MutablePropertySources()

private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);
            
public AbstractEnvironment() {
	customizePropertySources(this.propertySources);
}
複製代碼

PropertySource 接口

繼承體系如圖:

PropertySource

PropertySource 繼承體系來看,customizePropertySources 方法的調用鏈路是從子類一直往上調用 :

AbstractEnvironment -> StandardServletEnvironment -> StandardEnvironment

最終在 StandardEnvironment 使用 CopyOnWriteArrayList 數組進行屬性存儲

protected void customizePropertySources(MutablePropertySources propertySources) {
	propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
	propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
複製代碼

例如從上面能夠看出,propertySourceList 將會存儲系統的參數:

到時這些參數就能在啓動的應用中,經過上下文 context 進行獲取

((MutablePropertySources)((StandardEnvironment)context.environment).propertySources).propertySourceList
複製代碼

小結

剛纔一系列的前奏工做,只是用來識別路徑資源和加載系統參數

  • 設定構造器
  • 識別路徑變量
  • 設定環境參數:主要是 Environment 體系,還有在 propertySources 中保存了運行時的參數

Bean 的解析和註冊

Spring bean 的解析和註冊有一個重要的方法 refresh()

AbstractApplicationContext.refresh()

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing. (爲更新準備上下文,設定一些標誌)
		prepareRefresh();
		// Tell the subclass to refresh the internal bean factory. (告訴子類去更新它們的 bean factory)
		// 類的註冊到 bean factory 也是在這一步
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);
		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);
			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);
			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);
			// Initialize message source for this context.
			initMessageSource();
			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();
			// Initialize other special beans in specific context subclasses.
			onRefresh();
			// Check for listener beans and register them.
			registerListeners();
			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);
			// Last step: publish corresponding event.
			finishRefresh();
		}
		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}
			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();
			// Reset 'active' flag.
			cancelRefresh(ex);
			// Propagate exception to caller.
			throw ex;
		}
		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}
複製代碼

下面會圍繞這個方法進行跟蹤和分析。


prepareRefresh 準備更新

該方法做用:準備此上下文用於刷新、設置其啓動日期和 active 標誌,以及執行任何屬性源的初始化。

protected void prepareRefresh() {
	// Switch to active.
	// Initialize any placeholder property sources in the context environment.(空方法,等子類實現)
	initPropertySources();
	// Validate that all properties marked as required are resolvable:(校驗參數)
	// see ConfigurablePropertyResolver#setRequiredProperties
	getEnvironment().validateRequiredProperties();
	// Allow for the collection of early ApplicationEvents,
	// to be published once the multicaster is available...
	this.earlyApplicationEvents = new LinkedHashSet<>();
}
複製代碼

具體校驗的方法

org.springframework.core.env.AbstractPropertyResolver#validateRequiredProperties

public void validateRequiredProperties() {
	MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
	for (String key : this.requiredProperties) {
		if (this.getProperty(key) == null) {
			ex.addMissingRequiredProperty(key);
		}
	}
	if (!ex.getMissingRequiredProperties().isEmpty()) {
		throw ex;
	}
}
複製代碼

能夠看到,校驗邏輯是遍歷 requiredProperties,它是一個字符 Set,默認狀況下是空,表示不須要校驗任何元素,若是列表中有值,而後根據 key 獲取對應的環境變量爲空,將會拋出異常,致使 Spring 容器初始化失敗。


自定義環境變量校驗

既然給出了 requireProperties 列表,表示咱們可以往裏面自定義添加,須要校驗的環境變量:

  • 建立一個類,繼承自 AnnotationConfigServletWebServerApplicationContext,重載 initPropertySources
  • 在應用啓動時,將本身新建的類設定成應用上下文(application.setApplicationContextClass(CustomContext.class);)

例如:(引用自參考資料第五條)

public class CustomApplicationContext extends AnnotationConfigServletWebServerApplicationContext {
    @Override
    protected void initPropertySources() {
        super.initPropertySources();
        //把"MYSQL_HOST"做爲啓動的時候必須驗證的環境變量
        getEnvironment().setRequiredProperties("MYSQL_HOST");
    }
}


public static void main(String[] args) {
    SpringApplication springApplication = new SpringApplication(CustomizepropertyverifyApplication.class);
    springApplication.setApplicationContextClass(CustomApplicationContext.class);
    springApplication.run(args);
}
複製代碼

經過添加自定義的校驗值,在 Spring 應用啓動時,就能提早進行校驗


獲取 bean 容器

在這行代碼中 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

具體調用的是 :

org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory

protected final void refreshBeanFactory() throws BeansException {
	// 在更新時,若是發現已經存在,將會把以前的 bean 清理掉,而且關閉老 bean 容器
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		beanFactory.setSerializationId(getId());
		customizeBeanFactory(beanFactory);
		// 註釋 1.3 開始加載 (bean 註冊)
		loadBeanDefinitions(beanFactory);
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}
複製代碼

這個入口方法很重要,在這一步新建了 bean 容器和解析 bean,並將 bean 註冊到容器中。


BeanFactory 繼承體系

本次例子以及多數狀況下,使用的 bean 容器都是 DefaultListableBeanFactory,因此來介紹一下它的繼承體系:

DefaultListableBeanFactory

能夠看出,繼承體系十分龐大,繼承了多個註冊器和實現多個接口,經常使用的是單例 Singleton 註冊器和別名 Alias 註冊器,這兩個概念也很龐大,能夠先簡單熟悉下,知道容器默認的對象是單例模式,還有能夠經過別名來找到 bean,以後有機會再詳細介紹吧。


BanFactory 自定義

具體方法以下,經過這個方法,能夠對工廠進行定製化設置,讓子類進行自由配置:

org.springframework.context.support.AbstractRefreshableApplicationContext#customizeBeanFactory

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
	if (this.allowBeanDefinitionOverriding != null) {
		// 默認是 false,不容許覆蓋
		beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	if (this.allowCircularReferences != null) {
		// 默認是 false,不容許循環引用
		beanFactory.setAllowCircularReferences(this.allowCircularReferences);
	}
}
複製代碼

Bean 加載和解析

核心方法是這個:

org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// Create a new XmlBeanDefinitionReader for the given BeanFactory.
	// 爲給定的BeanFactory建立一個新的XmlBeanDefinitionReader
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
	// Configure the bean definition reader with this context's
	// resource loading environment.
	beanDefinitionReader.setEnvironment(this.getEnvironment());
	beanDefinitionReader.setResourceLoader(this);
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
	// Allow a subclass to provide custom initialization of the reader,
	// then proceed with actually loading the bean definitions.(空方法,讓子類進行擴展實現)
	initBeanDefinitionReader(beanDefinitionReader);
	loadBeanDefinitions(beanDefinitionReader);
}
複製代碼

在解析 XML 中,使用到如下兩個繼承體系:EntityResolverBeanDefinitionReader


EntityResolver

EntityResolver

接口全路徑是:org.xml.sax.EntityResolver,具體解析使用的方法是:

org.springframework.beans.factory.xml.ResourceEntityResolver#resolveEntity

該方法是用於解析 schemadtd,具體深究的話也很複雜,但解析 xml 不是我想了解的點,因此先跳過~


BeanDefinitionReader

BeanDefinitionReader

頂級接口是 BeanDefinitionReader,用於 XML Bean 定義的 Bean定義閱讀器。將實際讀取的 XML 文檔委託給實現。

這兩個類用途很明瞭,就是將 XML 轉成輸入流,感興趣的同窗能夠繼續深刻跟蹤~


配置文件加載

入口方法:(因爲有多個重名方法,因此複製路徑時,將參數的類型也拷貝了)

org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set<org.springframework.core.io.Resource>)

核心方法是這兩行

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    // 獲取資源文件(資源加載器從路徑識別資源文件)
    Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location)
    // 註釋 1.6 根據資源文件加載 bean
    int count = loadBeanDefinitions(resources);		
    ···
}
複製代碼

獲取資源文件後,開始解析資源文件(也就是一開始傳參的 config.xml),將它轉換成 Document

跟蹤代碼能夠看到,進行解析的資源文件從 Resource 包裝成 EncodeResouce,爲輸入流添加了字符編碼(默認爲 null),體現了設計模式 - 裝飾器模式

遍歷資源文件,進行轉換,核心方法是如下兩行:

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
	// 註釋 1.7 從資源文件中獲取輸入流
	InputStream inputStream = encodedResource.getResource().getInputStream();
	InputSource inputSource = new InputSource(inputStream);
	return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
複製代碼

Bean 解析

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
	// 註釋 1.8 將資源文件解析成 document
	Document doc = doLoadDocument(inputSource, resource);
	// 註釋 1.10 從 doc 和資源中解析元素,註冊到 bean factory
	int count = registerBeanDefinitions(doc, resource);
	if (logger.isDebugEnabled()) {
		logger.debug("Loaded " + count + " bean definitions from " + resource);
	}
	return count;
}
複製代碼

doLoadDocument() 方法中,將資源文件解析成 docuemnt 文檔

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	// 使用 DefaultBeanDefinitionDocumentReader 實例化 BeanDefinitionDocumentReader
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	// 記錄統計前 beanDefinition 的加載個數
	int countBefore = getRegistry().getBeanDefinitionCount();
	// 加載及註冊 bean,這裏使用註冊工廠的是 DefaultListableBeanFactory
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	// 記錄本次加載的 BeanDefinition 個數(新值 - 舊值)
	return getRegistry().getBeanDefinitionCount() - countBefore;
}
複製代碼

這裏很少介紹如何轉換成 documentdocumentReader 初始化,感興趣的同窗請繼續跟蹤~

下面要說的是 bean 容器 DefaultListableBeanFactory 解析 document

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions

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);
			// We cannot use Profiles.of(...) since profile expressions are not supported
			// in XML config. See SPR-12458 for details.
			if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
							"] not matching: " + getReaderContext().getResource());
				}
				return;
			}
		}
	}
	// preProcess 和 postProcess 點進去會發現是空方法,這兩個方法留給子類重載,體現了設計模式 - 模板方法
	preProcessXml(root);
	// 註釋 1.11 核心方法,解析 doc 元素
	parseBeanDefinitions(root, this.delegate);
	postProcessXml(root);
	this.delegate = parent;
}
複製代碼

從上面能夠看出,在解析以前,若是命名空間是以 http://www.springframework.org/schema/beans 開頭,將會檢查 profile 屬性

校驗經過後,開始正式解析 doc 元素

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	if (delegate.isDefaultNamespace(root)) {
		// 註釋 1.12 遍歷 doc 中的節點列表
		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)) {
					// 註釋 1.13 識別出默認標籤的 bean 註冊
					// 根據元素名稱,調用不一樣的加載方法,註冊 bean
					parseDefaultElement(ele, delegate);
				}
				else {
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		delegate.parseCustomElement(root);
	}
}
複製代碼

在這一步中,咱們在 xml 中配置的屬性就能對應到 document 對象中,在以後流程中取出使用


默認標籤解析

這部分不會細說,以後再寫一篇進行補充,因此簡單的過下代碼中,是如何解析默認標籤的

  • IMPORT:導入標籤
  • ALIAS:別名標籤
  • BEANbean 標籤
  • NESTED_BEANSbeans 標籤(嵌套的 beans)

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
	if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
		importBeanDefinitionResource(ele);
	}
	else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
		processAliasRegistration(ele);
	}
	else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
		processBeanDefinition(ele, delegate);
	}
	else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
		// recurse
		doRegisterBeanDefinitions(ele);
	}
}
複製代碼

讓咱們來看下如何解析 bean 標籤


bean 標籤解析

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#processBeanDefinition

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
	// 註釋 1.15 解析 bean 名稱的元素
	BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
	if (bdHolder != null) {
		bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
		try {
			// Register the final decorated instance. (註釋 1.16 註冊最後修飾後的實例)
			BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
		}
		catch (BeanDefinitionStoreException ex) {
			getReaderContext().error("Failed to register bean definition with name '" +
					bdHolder.getBeanName() + "'", ele, ex);
		}
		// Send registration event.
		getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
	}
}
複製代碼

下面講下幾個關鍵方法所作的事情


獲取 id 和 name

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
	// 獲取 ID 屬性
	String id = ele.getAttribute(ID_ATTRIBUTE);
	// 獲取 NAME 屬性
	String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
	List<String> aliases = new ArrayList<>();
	if (StringUtils.hasLength(nameAttr)) {
		// 名稱按照 , ; 進行分割
		String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
		aliases.addAll(Arrays.asList(nameArr));
	}
	String beanName = id;
	if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
		// 若是沒有指定 id,將 name 的第一個值做爲 id
		beanName = aliases.remove(0);
	}
	// 默認 null
	if (containingBean == null) {
		// 檢查名字是否惟一,若是 id 重複了,將拋出錯誤
		// 內部 usedNames 是一個 HashSet,將會存儲加載過的 name 和 aliases
		checkNameUniqueness(beanName, aliases, ele);
	}
	// 將公共屬性放入 AbstractBeanDefinition,具體實如今子類 GenericBeanDefinition
	AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
	if (beanDefinition != null) {
		if (!StringUtils.hasText(beanName)) {
			if (containingBean != null) {
				// 若是 id 和 name 都是空,那個 spring 會給它生成一個默認的名稱
				beanName = BeanDefinitionReaderUtils.generateBeanName(
						beanDefinition, this.readerContext.getRegistry(), true);
			}
			else {
				beanName = this.readerContext.generateBeanName(beanDefinition);
				String beanClassName = beanDefinition.getBeanClassName();
				if (beanClassName != null &&
						beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
						!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
					aliases.add(beanClassName);
				    }
			    }
		    }
		}
		String[] aliasesArray = StringUtils.toStringArray(aliases);
		return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
	}
	return null;
}
複製代碼

獲取 idname 屬性的流程,按照代碼註釋一步一步往下走就清晰了

該方法主要工做流程以下:

  • 提取元素中的 id name 屬性
  • 進一步解析其它全部屬性並統一封裝到 GenericBeanDefinition 類型的實例中
  • 檢測到 bean 沒有指定 beanName 使用默認規則生成 beanName
  • 將獲取到的信息封裝到 BeanDefinitionHolder 的實例中

對標籤中其它屬性的解析

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, java.lang.String, org.springframework.beans.factory.config.BeanDefinition)

public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) {
    AbstractBeanDefinition bd = createBeanDefinition(className, parent);
    parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
    bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
    parseMetaElements(ele, bd);
    parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
    parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
    parseConstructorArgElements(ele, bd);
    parsePropertyElements(ele, bd);
    parseQualifierElements(ele, bd);
    bd.setResource(this.readerContext.getResource());
    bd.setSource(extractSource(ele));
    return bd;
}
複製代碼

初始化 BeanDefiniton 在這個方法中:(具體實現是它的子類 GenericBeanDefinition 噢~)

BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader())

public static AbstractBeanDefinition createBeanDefinition( @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
	GenericBeanDefinition bd = new GenericBeanDefinition();
	bd.setParentName(parentName);
	if (className != null) {
		if (classLoader != null) {
			bd.setBeanClass(ClassUtils.forName(className, classLoader));
		}
		else {
			bd.setBeanClassName(className);
		}
	}
	return bd;
}
複製代碼

後面就是解析其它標籤的內容,以後會補坑~


BeanDefinition 繼承體系

GenericBeanDefinition

從圖中能夠看出,BeanDefinition 是一個接口,GenericBeanDefinitionRootBeanDefinitionChildBeanDefinition,這三者都繼承了 AbstractBeanDefinition

其中 BeanDefinition 是配置文件 <bean> 元素標籤在容器中的內部表示形式。

<bean> 元素標籤擁有 classscopelazy-init 等配置屬性,BeanDefinition 則提供了相應的 beanClassscopelazyInit 屬性,二者是互相對應的。


BeanDefinitionHolder 修飾

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinitionHolder, org.springframework.beans.factory.config.BeanDefinition)

public BeanDefinitionHolder decorateBeanDefinitionIfRequired( Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
	// 方法中的第三個參數是父類 bean
	// 當對某個嵌套配置進行分析時,這裏須要傳遞,是爲了使用父類的 scope 屬性,以備子類沒設定 scope,可使用父類的 scope 屬性
	BeanDefinitionHolder finalDefinition = definitionHolder;

	// Decorate based on custom attributes first.
	NamedNodeMap attributes = ele.getAttributes();
	// 遍歷全部的屬性,進行屬性的修飾
	for (int i = 0; i < attributes.getLength(); i++) {
		Node node = attributes.item(i);
		finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
	}

	// Decorate based on custom nested elements.
	NodeList children = ele.getChildNodes();
	// 遍歷全部的子節點,修飾子元素
	for (int i = 0; i < children.getLength(); i++) {
		Node node = children.item(i);
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
		}
	}
	return finalDefinition;
}
複製代碼

在以前的常規屬性解析後,在這一步操做中,主要用來完成自定義標籤元素的解析,這裏繼續留個坑~


Bean 註冊

經歷千辛萬苦,經過上面一些列的解析操做,終於到了註冊 bean 信息的方法

org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition

public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
		// Register bean definition under primary name.
		// 註釋 1.17 在 DefaultListableBeanFactory 的 beanDefinitionMap 中添加 bean 定義
		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);
			}
		}
	}
複製代碼

上面也說過,這裏使用的 bean 容器是 DefaultListableBeanFactory,註冊方法關鍵操做時如下兩行代碼:

org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
	this.beanDefinitionMap.put(beanName, beanDefinition);
	this.beanDefinitionNames.add(beanName);
}
複製代碼

到了這一步,將 bean 信息放入到 beanDefinitionMap,完成了類註冊的操做~

爲了描述代碼邏輯的完整性,對如下一些方法進行簡單介紹。


prepareBeanFactory

準備類加載器的環境,對前面獲取到的 beanFactory(ConfigurationListableBeanFactory) 進行相關的設置,包括 ClassLoader, post-processors


postProcessBeanFactory

將加載全部 bean 定義,但尚未實例化 bean 時,在應用程序上下文的標準初始化以後修改它的內部 bean 容器。

這容許在特定的 ApplicationContext 實現中註冊特殊的 beanpostprocessor 等。

這也是一個空方法,等子類去實現


invokeBeanFactoryPostProcessors

實例化並調用全部註冊的 BeanFactoryPostProcessorBean,這些是後處理器,處理類型是 BeanFactory, Spring 容器容許在實例化 bean 前,讀取 bean 信息和修改它的屬性。

至關於在實例化前,給用戶最後一次機會去修改 bean 信息。

還有一點,執行也能夠有前後順序,依據這些處理器是否實現 PriorityOrderedOrder 接口,根據 order 值進行排序。


registerBeanPostProcessors

實例化並註冊全部後處理器,跟上面的不同,這個方法處理的類型是 Bean ,跟上面方法同樣的是,也有優先級的概念~


initMessageSource

初始化此上下文的消息源


initApplicationEventMulticaster

初始化此上下文的事件多播程序


onRefresh

模板方法,可被重寫以添加特定於上下文的刷新工做。

在實例化單例以前調用特殊 bean 的初始化。(霧,不知道是啥特殊 bean ,留個坑=-=)

此實現爲空。


registerListeners

檢查偵聽器 bean 並註冊它們

事件監聽者類型是 java.util.EventListener


finishBeanFactoryInitialization

完成 bean 容器的初始化,實例化全部剩餘的(非惰性初始化)單例


finishRefresh

最後一步,發佈相應的事件

事件的類型是:java.util.EventObject


resetCommonCaches

真真註冊的最後一步,用來清除緩存

重置 Spring 核心中的公共內省緩存,由於咱們可能不再須要單例 bean 的元數據了


總結

本章筆記只是記錄了一個 bean 如何從 xml 加載到 bean 容器的註冊表中,經歷了多行代碼,終於摸清調用鏈路。

這裏總結一下核心的 loadBeanDefinitions(beanFactory) 工做流程:

① 讀取配置文件

  • 封裝資源文件:獲取路徑文件,封裝成 EncodeResource
  • 獲取輸入流:從 Resource 中獲取對應的 InputStream 並構造 InputSource
  • 傳遞參數:經過構造的 InputSource 實例和 Resource 實例,傳遞給 doLoadBeanDefinitions 方法

② 加載 bean

  • 獲取對 XML 資源文件的驗證模式
  • 加載 XML 資源文件,解析成對應的 Document 文檔:裏面有多個 Node 節點信息,保存了咱們寫的配置信息
  • 根據 Document 文件進行 Bean 信息解析

bean 標籤的解析和註冊

  • 委託 BeanDefinitionDelegate 類的 parseBeanDefinitionElement 方法:對元素進行解析,返回 BeanDefinitionHolder 類型的實例(裏面包含了 classnameidalias等屬性)
  • 解析標籤:判斷標籤類型,看解析的是默認標籤仍是自定義標籤
  • bdHodler 進行註冊:解析完成後,註冊 bean 信息,註冊操做委託給了 BeanDefinitionReaderUtilsregisterBeanDefinition 方法
  • 發送響應事件:通知相關的監聽器,通知 bean 容器已經加載完成

下一篇筆記再會~


踩坑記錄

Javadoc generation failed. Generated Javadoc options file (useful for troubleshooting)

在編譯時,發現沒法成功,提示 Javadoc 的錯誤,解決方法是在 gradle 文件中添加如下配置:

tasks.withType(Javadoc) {
    options.addStringOption('Xdoclint:none', '-quiet')
    options.addStringOption('encoding', 'UTF-8')
}
複製代碼

參考資料

一、spring-analysis/note/Spring.md

二、Spring Framework 5.0.0.M3中文文檔

三、Spring 源碼深度解析 / 郝佳編著. -- 北京 : 人民郵電出版社

四、使用Spring3.1後的的Profile配置使不一樣環境加載不一樣配置文件

五、spring4.1.8擴展實戰之一:自定義環境變量驗證

傳送門:

相關文章
相關標籤/搜索