[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";
}
複製代碼
使用 ClassPathXmlApplicationContext
從 xml
配置文件中獲取 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
的繼承體系結構圖:
這種結構圖是經過 IDEA
編輯器的 Diagrams
功能展現的,對當前類右鍵選擇,能夠看到繼承體系,繼承了哪些類和引用了哪些接口,方便咱們去了解~
ClassPathXmlApplicationContext
繼承自 AbstractApplicationContext
,而 AbstractRefreshableApplicationContext
是 AbstractApplicationContext
的抽象子類,使用的類註冊工廠是 DefaultListableBeanFactory
,這個註冊工廠也很重要,後面會有它的介紹。
簡單來講,DefaultListableBeanFactory
是 Spring
註冊及加載 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);
}
複製代碼
咱們有不一樣的運行環境,dev
,test
或者 prod
,這個時候加載的配置文件和屬性應該有所不一樣,這個時候就須要使用到 Environment
來進行區分。
Spring
環境和屬性是由四個部分組成:
Environment
: 環境,由 Profile
和 PropertyResolver
組合。Profile
: 配置文件,能夠理解爲,容器裏多個配置組別的屬性和 bean
,只有激活的 profile
,它對應的組別屬性和 bean
纔會被加載PropertySource
: 屬性源, 使用 CopyOnWriteArrayList
數組進行屬性對 key-value
形式存儲PropertyResolver
:屬性解析器,這個用途就是解析屬性首先來看 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);
}
複製代碼
經過這個屬性,能夠同時在配置文件中部署兩套配置,用來適用於生產環境和開發環境,這樣能夠方便的進行切換開發、部署環境,經常使用來更換不一樣的數據庫或者配置文件。
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
官方註釋描述:
/** * 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
繼承體系來看,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
中保存了運行時的參數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();
}
}
}
複製代碼
下面會圍繞這個方法進行跟蹤和分析。
該方法做用:準備此上下文用於刷新、設置其啓動日期和 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
註冊到容器中。
本次例子以及多數狀況下,使用的 bean
容器都是 DefaultListableBeanFactory
,因此來介紹一下它的繼承體系:
能夠看出,繼承體系十分龐大,繼承了多個註冊器和實現多個接口,經常使用的是單例 Singleton
註冊器和別名 Alias
註冊器,這兩個概念也很龐大,能夠先簡單熟悉下,知道容器默認的對象是單例模式,還有能夠經過別名來找到 bean
,以後有機會再詳細介紹吧。
具體方法以下,經過這個方法,能夠對工廠進行定製化設置,讓子類進行自由配置:
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);
}
}
複製代碼
核心方法是這個:
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
中,使用到如下兩個繼承體系:EntityResolver
和 BeanDefinitionReader
接口全路徑是:org.xml.sax.EntityResolver
,具體解析使用的方法是:
org.springframework.beans.factory.xml.ResourceEntityResolver#resolveEntity
該方法是用於解析 schema
和 dtd
,具體深究的話也很複雜,但解析 xml
不是我想了解的點,因此先跳過~
頂級接口是 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());
}
複製代碼
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;
}
複製代碼
這裏很少介紹如何轉換成 document
和 documentReader
初始化,感興趣的同窗請繼續跟蹤~
下面要說的是 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
對象中,在以後流程中取出使用
這部分不會細說,以後再寫一篇進行補充,因此簡單的過下代碼中,是如何解析默認標籤的
bean
標籤beans
標籤(嵌套的 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
標籤
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));
}
}
複製代碼
下面講下幾個關鍵方法所作的事情
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;
}
複製代碼
獲取 id
和 name
屬性的流程,按照代碼註釋一步一步往下走就清晰了
該方法主要工做流程以下:
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
、RootBeanDefinition
、ChildBeanDefinition
,這三者都繼承了 AbstractBeanDefinition
。
其中 BeanDefinition
是配置文件 <bean>
元素標籤在容器中的內部表示形式。
<bean>
元素標籤擁有 class
、 scope
、 lazy-init
等配置屬性,BeanDefinition
則提供了相應的 beanClass
、 scope
、lazyInit
屬性,二者是互相對應的。
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
信息的方法
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
,完成了類註冊的操做~
爲了描述代碼邏輯的完整性,對如下一些方法進行簡單介紹。
準備類加載器的環境,對前面獲取到的 beanFactory(ConfigurationListableBeanFactory)
進行相關的設置,包括 ClassLoader
, post-processors
等
將加載全部 bean
定義,但尚未實例化 bean
時,在應用程序上下文的標準初始化以後修改它的內部 bean
容器。
這容許在特定的 ApplicationContext
實現中註冊特殊的 beanpostprocessor
等。
這也是一個空方法,等子類去實現
實例化並調用全部註冊的 BeanFactoryPostProcessorBean
,這些是後處理器,處理類型是 BeanFactory
, Spring
容器容許在實例化 bean
前,讀取 bean
信息和修改它的屬性。
至關於在實例化前,給用戶最後一次機會去修改 bean
信息。
還有一點,執行也能夠有前後順序,依據這些處理器是否實現 PriorityOrdered
、Order
接口,根據 order
值進行排序。
實例化並註冊全部後處理器,跟上面的不同,這個方法處理的類型是 Bean
,跟上面方法同樣的是,也有優先級的概念~
初始化此上下文的消息源
初始化此上下文的事件多播程序
模板方法,可被重寫以添加特定於上下文的刷新工做。
在實例化單例以前調用特殊 bean
的初始化。(霧,不知道是啥特殊 bean
,留個坑=-=)
此實現爲空。
檢查偵聽器 bean
並註冊它們
事件監聽者類型是 java.util.EventListener
完成 bean
容器的初始化,實例化全部剩餘的(非惰性初始化)單例
最後一步,發佈相應的事件
事件的類型是:java.util.EventObject
真真註冊的最後一步,用來清除緩存
重置
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
類型的實例(裏面包含了 class
、 name
、 id
、 alias
等屬性)bdHodler
進行註冊:解析完成後,註冊 bean
信息,註冊操做委託給了 BeanDefinitionReaderUtils
的 registerBeanDefinition
方法bean
容器已經加載完成下一篇筆記再會~
在編譯時,發現沒法成功,提示 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配置使不一樣環境加載不一樣配置文件