Spring Ioc源碼分析系列--Ioc源碼入口分析 本系列文章代碼基於Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源碼分析系列--Ioc的基礎知識準備介紹了Ioc的基礎概念以及Spring Ioc體系的部分基礎知識。那麼這一篇就會真正通過一個例子,啟動Ioc ...
Spring Ioc源碼分析系列--Ioc源碼入口分析
本系列文章代碼基於Spring Framework 5.2.x
前言
上一篇文章Spring Ioc源碼分析系列--Ioc的基礎知識準備介紹了Ioc的基礎概念以及Spring Ioc體系的部分基礎知識。那麼這一篇就會真正通過一個例子,啟動Ioc容器,獲取容器里的bean
。
首先說明,本文的例子是基於xml
配置文件去完成的。
為什麼是xml
?因為xml
是Spring的靈魂,可能我們初學Spring都會有畏難情緒,看到繁雜的xml
就會打退堂鼓。但是實際上不然,xml
的格式是相當清晰的,一個配置文件可以說沒有一行配置是多餘的。現在大部分的配置是用註解去完成的,相比xml
而言是簡潔許多,但是對於我們初學而言,xml
其實是更好的方式,xml
相對於註解而言是相對繁雜,但是它的信息也更多更明確,註解只是添加了一個註解就完成配置,細節上是更為隱蔽的。再加上xml
配置文件和註解配置的原理是相通的,核心思想是一樣的,掌握核心就萬變不離其宗。所以這系列文章的例子大部分都會採取xml
的方式去配置,當然後續可能也會補充一下註解方式的例子和分析文章。
萬事開頭難,如果實在覺得看不懂但又想學的,可以硬著頭皮看下去,等以後回過頭來再看的時候,會有豁然開朗的感覺。
源碼分析
啟動容器示例
廢話少說,下麵開始搞個例子分析一下。所有源碼都在我的倉庫ioc-sourcecode-analysis-code-demo里找到。
首先弄個實體類User
/**
* @author Codegitz
* @date 2022/4/26 10:58
**/
public class User {
private String id;
private String name;
private String age;
}
創建業務類UserService
以及業務實現類UserServiceImpl
,這裡的邏輯很簡單,就是根據傳入的name
和age
返回一個新的user
對象。
/**
* @author Codegitz
* @date 2022/4/26 10:59
**/
public interface UserService {
User getUser(String name,String age);
}
public class UserServiceImpl implements UserService {
@Override
public User getUser(String name, String age) {
User user = new User();
user.setId("1");
user.setName(name);
user.setAge(age);
return user;
}
}
業務類的準備工作已經完成了,接下來就是要寫個xml配置文件,告訴Spring Ioc我需要一個UserService
的bean
。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="io.codegitz.service.impl.UserServiceImpl"/>
</beans>
這個xml配置文件比較簡單,我們來解釋一下每一行是什麼意思。
<?xml version="1.0" encoding="UTF-8"?>
為xml文件的規定頭,沒啥好說的。
xmlns="http://www.springframework.org/schema/beans"
表明瞭xml的命名空間,xmlns
全稱為xml namespace
,一個xml裡面命名空間可以有多個。
xmlns:xsi
和 xsi:schemaLocation
是指明瞭xml文件的驗證模型和驗證模型文件的位置。可以看到驗證模型能通過http
方式獲取,但是為了防止網路抖動的影響,一般會在本地存放驗證文件。spring-beans.xsd
就可以在圖示的目錄下找到。
接下來就到了<bean id="userService" class="io.codegitz.service.impl.UserServiceImpl"/>
這一行,這是我們獲取一個Bean的關鍵配置,這裡告訴Ioc我需要一個id
為userService
,class
為io.codegitz.service.impl.UserServiceImpl
的Bean。
到這裡配置已經完成了,接下來創建一個引導類啟動Ioc就能獲取到這個bean了。
新建引導類Application
,通過ClassPathXmlApplicationContext
類引導啟動Ioc容器,這是載入xml
配置的常用引導類。
/**
* @author Codegitz
* @date 2022/4/26 10:57
**/
public class Application {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
User codegitz = userService.getUser("codegitz", "25");
System.out.println(codegitz);
}
}
到這裡其實已經完成了所有樣例的準備,可以啟動容器了。
可以看到,只是簡單的ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
就可以啟動一個容器,隨後就能通過getBean()
方法獲取容器里的bean了,接下來我們看看new ClassPathXmlApplicationContext("beans.xml")
發生了什麼。
容器啟動分析
ClassPathXmlApplicationContext構造方法
這裡我們使用的是ClassPathXmlApplicationContext
,那麼就從這個類的構造方法開始分析。
摘取的代碼片段我會保留原文註釋,看官可以細細品味。
/**
* Create a new ClassPathXmlApplicationContext, loading the definitions
* from the given XML file and automatically refreshing the context.
* @param configLocation resource location
* @throws BeansException if context creation failed
*/
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
繼續跟進構造函數,根據方法上的註釋和代碼可以看到。這裡就做了兩件事,根據給定的xml配置文件載入BeanDefinition,然後調用refresh()
方法刷新容器。
/**
* Create a new ClassPathXmlApplicationContext with the given parent,
* loading the definitions from the given XML files.
* @param configLocations array of resource locations
* @param refresh whether to automatically refresh the context,
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
* @param parent the parent context
* @throws BeansException if context creation failed
* @see #refresh()
*/
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// 初始化父類
super(parent);
// 設置配置文件信息,這裡會進行占位符的替換
// 例如/${env}-beans.xml類型的路徑會被占位符替換,如果env=sit,那麼路徑就會被替換為/sit-beans.xml
setConfigLocations(configLocations);
// 如果沒有刷新,就會調用refresh()方法進行刷新,這個方法是整個Ioc的關鍵入口,由父類AbstractApplicationContext實現
if (refresh) {
refresh();
}
}
前面兩個方法比較簡單,可以跟著註釋看一下。重點在refresh()
方法。這個方法會完成Ioc所需要的所有配置載入,完成所有bean的註冊以及Spring的一系列實現。
refresh()方法
到這裡,才是真正摸到了Ioc的源碼入口。看一下refresh()
方法的代碼。
public void refresh() throws BeansException, IllegalStateException {
//給容器refresh加鎖,避免容器在refresh階段時,容器進行了初始化或者銷毀操作
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//調用容器準備刷新的方法,獲取容器當前時間,同時給容器設置同步標識、具體方法
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
/**
* 告訴子類啟動refreshBeanFactory方法,refreshBeanFactory方法會載入bean的定義文件,該方法的實現會針對xml配置,最終創建內部容器
* 該容器負責bean的創建與管理,會進行BeanDefinition的註冊
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//註冊一些容器中需要使用的系統bean,例如classloader,BeanFactoryPostProcessor等
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses
// 允許容器的子類去註冊PostProcessor,鉤子方法.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
//激活容器中註冊為bean的BeanFactoryPostProcessor
//對於註解容器,org.springframework.context.annotation.ConfigurationClassPostProcessor
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()
、obtainFreshBeanFactory()
和prepareBeanFactory(beanFactory)
三個方法。
prepareRefresh()方法
跟進prepareRefresh()
方法,這個方法主要是做了一些初始化屬性設置和校驗。
/**
* Prepare this context for refreshing, setting its startup date and
* active flag as well as performing any initialization of property sources.
*/
protected void prepareRefresh() {
// Switch to active.
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
// 省略部分日誌...
// Initialize any placeholder property sources in the context environment.
// 初始化一些屬性,交由子類實現
initPropertySources();
// Validate that all properties marked as required are resolvable:
// see ConfigurablePropertyResolver#setRequiredProperties
// 校驗是否有必須的屬性,如果有必須的屬性但是環境沒配置,則拋出異常
getEnvironment().validateRequiredProperties();
// Store pre-refresh ApplicationListeners...
// 存儲在refresh之前註冊的ApplicationListener,避免這部分的ApplicationListener在後續的調用中被丟失
// 這是一個2019年修複的bug
if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
// Reset local application listeners to pre-refresh state.
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();
}
這個裡面比較簡單,沒啥好看。稍微來看下getEnvironment().validateRequiredProperties()
的代碼。
/**
* Return the {@code Environment} for this application context in configurable
* form, allowing for further customization.
* <p>If none specified, a default environment will be initialized via
* {@link #createEnvironment()}.
*/
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
/**
* Create and return a new {@link StandardEnvironment}.
* <p>Subclasses may override this method in order to supply
* a custom {@link ConfigurableEnvironment} implementation.
*/
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
可以看到這裡返回的是StandardEnvironment
類型Environment
,這裡的邏輯就是有則返回,無則創建。接下來看validateRequiredProperties()
方法。最終實現是在AbstractPropertyResolver#validateRequiredProperties()
方法。
@Override
public void validateRequiredProperties() {
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
// 遍歷requiredProperties,逐個去當前環境獲取是否存在
for (String key : this.requiredProperties) {
if (this.getProperty(key) == null) {
ex.addMissingRequiredProperty(key);
}
}
// 如果存在缺失的必須屬性,則拋出異常
if (!ex.getMissingRequiredProperties().isEmpty()) {
throw ex;
}
}
prepareRefresh()
方法比較簡單,到此已經基本看完,接下來看下一個obtainFreshBeanFactory()
方法。
obtainFreshBeanFactory()方法
obtainFreshBeanFactory()
方法從名字上就知道是獲取並且同時刷新一個beanfactory
。
在沒有分析代碼之前,先來猜測一下它的實現。我們知道它最終會返回一個可以使用的beanfactory
,說明此時在beanfactory
里已經初始化完成了我們定義的BeanDefinition
,那麼這裡應該會有一些基礎信息的配置,然後解析xml
文件,獲取xml
配置信息,然後初始化相應的BeanDefinition
,準備就緒後返回beanfactory
。當然這隻是猜測,具體實現如何,馬上分析。
那麼接下來就跟進這個方法看一下代碼實現。
/**
* Tell the subclass to refresh the internal bean factory.
* @return the fresh BeanFactory instance
* @see #refreshBeanFactory()
* @see #getBeanFactory()
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 調用子類的實現,刷新beanFactory
refreshBeanFactory();
// 返回刷新完成(即啟動完成)的beanFactory
return getBeanFactory();
}
跟進refreshBeanFactory()
方法,關鍵地方都已經加上註釋,也比較簡單易懂,跟著看一下就行。
/**
* This implementation performs an actual refresh of this context's underlying
* bean factory, shutting down the previous bean factory (if any) and
* initializing a fresh bean factory for the next phase of the context's lifecycle.
* 此實現執行此上下文的底層 bean 工廠的實際刷新,
* 關閉先前的 bean 工廠(如果有)併為上下文生命周期的下一階段初始化一個新的 bean 工廠。
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
// 如果有前一個,先關閉
if (hasBeanFactory()) {
// 先銷毀所有已經註冊的bean
destroyBeans();
// 關閉工廠,將this.beanFactory設為null
closeBeanFactory();
}
try {
//創建DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
//為了序列化指定id,如果需要的話,讓這個beanFactory從id反序列化到BeanFactory對象
beanFactory.setSerializationId(getId());
//定製beanFactory,設置相關屬性,包括是否允許覆蓋同名稱的不同定義的對象已經迴圈依賴
//以及設置@Autowire和@Qualifier註冊解析器QualifierAnnotationAutowire-CandidateResolver
customizeBeanFactory(beanFactory);
//初始化DocumentReader,併進行xml文件讀取和解析
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
前面的準備工作是比較簡單的,不再細說。重點在loadBeanDefinitions(beanFactory)
方法,這裡會載入xml
獲取我們配置的BeanDefinition
。
記住這個位置AbstractRefreshableApplicationContext#refreshBeanFactory()
,這是經過前面繁雜的摸索後真正進入BeanDefinition載入的分水嶺。
跟進代碼查看。
/**
* Loads the bean definitions via an XmlBeanDefinitionReader.
* 通過 XmlBeanDefinitionReader 載入 bean 定義。
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see #initBeanDefinitionReader
* @see #loadBeanDefinitions
*/
@Override
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 進行環境變數的設置
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.
// 允許子類提供閱讀器的自定義初始化,然後繼續實際載入 bean 定義。
// 對 beanDefinitionReader 進行屬性設置
initBeanDefinitionReader(beanDefinitionReader);
// 載入 bean 定義
loadBeanDefinitions(beanDefinitionReader);
}
可以看到這裡也是進行了一波準備工作,首先是給beanFactory
創建一個XmlBeanDefinitionReader
,後續xml解析都是由它來完成,接下來對這個XmlBeanDefinitionReader
進行一些屬性設置,接下來調用loadBeanDefinitions(beanDefinitionReader)
載入BeanDefinition
,跟進代碼查看。
/**
*
* 使用給定的 XmlBeanDefinitionReader 載入 bean 定義。
* <p>bean 工廠的生命周期由 {@link refreshBeanFactory} 方法處理;因此這個方法只是應該載入和或註冊 bean 定義。
*
* 在初始化了DefaultListableBeanFactory和XmlBeanDefinitionReader後就可以進行配置文件的讀取了
*
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 返回一個引用構建此上下文的 XML bean 定義文件的 Resource 對象數組, 預設實現返回 {@code null}。
// 子類可以覆蓋它以提供預構建的資源對象而不是占位符字元串。
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 返回引用構建此上下文的 XML bean 定義文件資源位置數組,還可以包括占位符模式,這將通過 ResourcePatternResolver 進行解析獲取。
// <p>預設實現返回 {@code null}。子類可以覆蓋它以提供一組資源位置以從中載入 bean 定義。
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
可以看到,由於沒有預構建的資源,所以configResources
會為null
,configLocations
則獲取到了我們的beans.xml
,那麼接下來就會解析beans.xml
獲取BeanDefinition
。
跟進reader.loadBeanDefinitions(configLocations)
方法,具體的實現是在 AbstractBeanDefinitionReader#loadBeanDefinitions(String... locations)
方法。
@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;
}
繼續套娃,進入loadBeanDefinitions(location)
方法。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
//獲取資源載入器,主要功能是根據路徑和類載入器獲取Resource對象
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
//ResourcePatternResolver用於載入多個文件或者載入Ant風格路徑的資源文件
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// 以Resource形式返回所有配置文件
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 通過resources載入
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
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 {
/**
* 載入單個資源,直接使用ResourceLoader載入
*/
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
這裡會經過一系列的重載方法,最終會來到doLoadBeanDefinitions()
方法里,這才是真正解析的地方,好家伙,山路十八彎。
跟進doLoadBeanDefinitions()
方法。
/**
* 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 {
/**
* 創建Document對象,XML文檔對象,即dom樹
* 使用這個Document對象可以獲取XML文件中的節點並且創建節點
* SAX XML
* 解析dom樹,即解析出一個個屬性,將其屬性保存到BeanDefinition當中
* 並向容器註冊BeanDefinition
*/
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
// 省略部分異常信息
}
獲取Document
對象就不說了,可以單獨出一篇文章Spring Ioc源碼分析系列--獲取xml文件Document對象,這裡再跟進去就離主題太遠了。
跟進registerBeanDefinitions(doc, resource)
方法。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//創建BeanDefinitionDocumentReader,這個是實際從XML的dom樹中服務BeanDefinition
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//獲取註冊表BeanDefinitionMap在本次載入前的BeanDefinition數量
int countBefore = getRegistry().getBeanDefinitionCount();
//載入並註冊
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//用本次載入後的數據減去以前有的數量,即為本次載入的BeanDefinition數量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
重點在方法documentReader.registerBeanDefinitions(doc, createReaderContext(resource))
里,跟進代碼。
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.
//BeanDefinition的解析委托類
BeanDefinitionParserDelegate parent = this.delegate;
//判斷這個根節點是否是預設的命名空間
//底層就是判斷這個根節點的namespace="http://www.springframework.org/schema/beans"
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
//獲取這個profile的值,表示剖面,用於設置環境
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;
}
}
}
//解析XML之前做的準備工作,其實什麼也沒做
preProcessXml(root);
//調用這個方法解析
parseBeanDefinitions(root, this.delegate);
//後續處理
postProcessXml(root);
this.delegate = parent;
}
跟進解析BeanDefinition的方法parseBeanDefinitions(root, this.delegate)
裡面。
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;
//Bean定義的document對象使用了spring的預設命名空間,如http://www.springframework.org/schema/beans
if (delegate.isDefaultNamespace(ele)) {
//若是則按照spring原有的邏輯進行解析
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);
}
//若果是別名元素,則進行別名解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//bean元素解析
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
顯然我們的例子只有一個Bean標簽,所以會進入到processBeanDefinition()
方法里。
/**
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//BeanDefinitionHolder是對BeanDefinition的封裝,即bean定義的封裝類
//對document對象中的bean標簽解析由BeanDefinitionParserDelegate實現
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//從springIOC容器註冊解析得到的BeanDefinition,這是BeanDefinition向IOC容器註冊的入口
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));
}
}
調用BeanDefinitionReaderUtils.registerBeanDefinition()
方法真正將BeanDefinition註冊進容器里。咋註冊呢?其實就是加到BeanFactory
的beanDefinitionMap
屬性里。beanDefinitionMap
可以說就是BeanDefinition的容器了。
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
// 這裡真正把BeanDefinition註冊到了BeanFactory里
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);
}
}
}
到這裡已經完成了BeanDefinition的註冊,是不是有點曲折?其實很簡單,就是細節比較多。
到這裡obtainFreshBeanFactory()
方法已經基本結束,已經完成了配置文件的解析並且註冊BeanDefinition的過程了。剩下的操作就是把這個BeanFactory返回給上一步。
prepareBeanFactory()方法
接下來分析一下prepareBeanFactory()
方法,這方法也簡單,主要就是對BeanFactory進行一些屬性的設置,跟著註釋看一下就行。
/**
* Configure the factory's standard context characteristics,
* such as the context's ClassLoader and post-processors.
* @param beanFactory the BeanFactory to configure
*/
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Tell the internal bean factory to use the context's class loader etc.
// 設置 beanFactory的classLoader 為當前 context的classLoader
beanFactory.setBeanClassLoader(getClassLoader());
/**
* 設置 beanFactory的表達式語言處理器,Spring3 增加了表達式語言的支持,
* 預設可以使用#{bean.xxx}的形式來調用相關屬性值
* @Qusetion 在註冊了這個解析器之後,spring是在什麼時候調用這個解析器的呢?
* Spring在bean進行初始化的時候會有屬性填充的步驟,而在這一步中
* Spring會調用AbstractAutowireCapableBeanFactory類的applyPropertyValues函數來完成功能。
* 就這個函數中,會通過構造 BeanDefinitionValueResolver類型實例valueResolver來進行屬性值的解析。
* 同時,也是在這個步驟中一般通過 AbstractBeanFactory 中的 evaluateBeanDefinitionString
* 方法去完成SpEL解析
*/
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
//為beanFactory增加一個PropertyEditor,這個主要是對bean的屬性等設置管理的一個工具
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
// Configure the bean factory with context callbacks.
//添加beanPostProcessor
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
//設置幾個忽略自動裝配的介面
// aware都是由invokeAware方法註入,忽略自動Autowire
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
// BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
// 設置了幾個自動裝配的規則,後面三個都是把當前對象註冊了進去,只有BeanFactory老老實實得註冊了一個BeanFactory
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
// Register early post-processor for detecting inner beans as ApplicationListeners.
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
// Detect a LoadTimeWeaver and prepare for weaving, if found.
// 添加對AspectJ的支持
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
// Register default environment beans.添加系統環境預設的bean
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}
到這裡refresh()的前面三個方法已經簡單過完了,除瞭解析配置文件複雜點,其他的都是些屬性配置居多。
小結
本文先通過一個列子驅動,找到了Ioc容器啟動的入口,簡單分析了一下前三個方法,最重要的就是載入BeanDefinition的過程了,可以仔細看多幾遍。
今天發生了點小插曲,本該寫得更詳細點的,但是寫不了了,只能作罷,將就著看吧。
如果有人看到這裡,那在這裡老話重提。與君共勉,路漫漫其修遠兮,吾將上下而求索。