Spring原始碼分析:BeanFactory (二)
前言
在前面我們簡單的分析了BeanFactory的結構,ListableBeanFactory,HierarchicalBeanFactory,AutowireCapableBeanFactory。主要核心類DefaultListableBeanFactory,通過程式設計啟動IOC容器 將BeanFactory的功能逐漸的剝離開來,方便我們理解整個架構。
回顧一下程式碼:
ClassPathResource resource = new ClassPathResource("spring.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(resource); MyBean bean = factory.getBean(MyBean.class); System.out.println(bean.toString());
前面我們分析了Spring的統一資源載入策略(Resource介面),ClassPathResource 在類路徑下查詢檔案,但是Resource 只是查詢資源,並不負責讀取和註冊bean。
DefaultListableBeanFactory 實現了 BeanDefinitionRegistry
介面,具有了註冊bean的功能,
讀取資源則通過單獨的模組來實現,這裡委託為XmlBeanDefinitionReader
來讀取xml配置檔案
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { super(registry); }
ApplicationContext
在前面我們可以很方便的通過程式設計的方式來手工控制這些配置的容器的建立過程了,但是,在Spring 中,系統以及為我們提供了許多已經定義好的容器的實現,如果說BeanFactory是Spring的"心臟",那麼ApplicationContext
就是完整的"身軀"了。ApplicationContext由BeanFactory派生而來,提供了更多面向實際應用的功能,所以說,ApplicationContext是一個高階形態意義的IOC容器,下面我們就來慢慢的認識一下它。
再來看一段程式碼:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); MyBean bean = context.getBean(MyBean.class); System.out.println(bean.toString());
這個看起來是不是比 DefaultListableBeanFactory 簡單多了,其實所謂的高階容器也就是把最基礎的容器進行了封裝,因此在最開始時我們才使用最基礎的BeanFactory來展示,這樣更容易理解。
體系結構
- 支援不同的資訊源,我們看到ApplicationContext 擴充套件的MessageSource介面,這些資訊源的擴充套件功能可以支援國際化的實現,為開發多語言版本的應用提供服務。
- 訪問資源。這一特性體現在對ResourceLoader和Resource的支援上,這樣我們可以從不同地方得到Bean定義資源。這種抽象使 使用者可以靈活的定義Bean定義資訊,尤其是從不同的I/O途徑得到Bean定義資訊。
- 支援應用事件。繼承了介面ApplicationEventPublisher,從而在上下文中引入了事件機制。這些事件和Bean的生命週期的結合為Bean的管理提供了便利。
看到如上的繼承體系,應該就更能明白ApplicationContext 是Spring BeanFactory的高階形態的容器了。
介面方法
@Nullable
String getId();
/**
* Return a name for the deployed application that this context belongs to.
* @return a name for the deployed application, or the empty String by default
*/
String getApplicationName();
/**
* Return a friendly name for this context.
* @return a display name for this context (never {@code null})
*/
String getDisplayName();
/**
* Return the timestamp when this context was first loaded.
* @return the timestamp (ms) when this context was first loaded
*/
long getStartupDate();
/**
* Return the parent context, or {@code null} if there is no parent
* and this is the root of the context hierarchy.
* @return the parent context, or {@code null} if there is no parent
*/
@Nullable
ApplicationContext getParent();
/**
* Expose AutowireCapableBeanFactory functionality for this context.
* <p>This is not typically used by application code, except for the purpose of
* initializing bean instances that live outside of the application context,
* applying the Spring bean lifecycle (fully or partly) to them.
* <p>Alternatively, the internal BeanFactory exposed by the
* {@link ConfigurableApplicationContext} interface offers access to the
* {@link AutowireCapableBeanFactory} interface too. The present method mainly
* serves as a convenient, specific facility on the ApplicationContext interface.
* <p><b>NOTE: As of 4.2, this method will consistently throw IllegalStateException
* after the application context has been closed.</b> In current Spring Framework
* versions, only refreshable application contexts behave that way; as of 4.2,
* all application context implementations will be required to comply.
*/
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
在ApplicationContext容器中,我們以常用的ClassPathXmlApplicationContext
的實現來分析ApplicationContext容器的設計原理
ClassPathXmlApplicationContext
體系結構
在ClassPathXmlApplicationContext 的設計中,其主要的功能在基類AbstractXmlApplicationContext
中已經實現了。
原始碼分析
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
ClassPathXmlApplicationContext 看起來還是很簡單的,主要的實現都在基類裡面實現了,這是隻是負責呼叫。
父容器的設定在 AbstractApplicationContext
中
/** Parent context */
@Nullable
private ApplicationContext parent;
@Override
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
如果存在父容器,那麼會合並兩者的環境配置Environment。這裡Environment並不是我們的重點。
這個 refresh()
過程會牽涉IOC容器啟動的一系列複雜操作,同時,對於不同容器的實現,這些操作都是類似的,因此在基類中將它們封裝好。所以,我們在ClassPathXmlApplicationContext 的設計中看到的只是一個簡單的呼叫。關於這個refresh()
在IOC容器啟動時的具體實現,這個在後面再來分析,這裡就不展開了。
IOC容器的初始化過程
簡單來說,IOC容器的初始化由前面介紹的refresh()方法來啟動的,這個方法標誌著IOC容器的正式啟動。具體來說,這個啟動包括BeanDefinition的Resouce定位、載入和註冊三個基本過程。如果我們瞭解如何程式設計式地使用IOC容器,就可以清楚得看到Resource定位和載入過程的介面呼叫。在下面的內容裡,我們將會詳細分析這三個過程的實現。
Spring把這三個過程分開,並使用不同的模組來完成,如使用響應的ResourceLoader、BeanDefinitionReader等模組,通過這樣的設計方式,可以讓使用者更加靈活地對這三個過程進行剪裁或擴充套件,定義出最適合自己的IOC容器的初始化過程。
-
Resource 定位
第一個過程就是Resource 定位過程。這個Resource 定位指的是BeanDefinition的資源定位,它由ResourceLoader通過統一的Resource介面來完成,這個Resource對各種形式的BeanDefinition的使用都提供了統一介面。
-
BeanDefinition的載入
第二個過程是BeanDefinition的載入。這個載入過程是把使用者定義好的Bean表示成IOC容器內部的資料結構,而這個容器內部的資料結構就是BeanDefinition,具體來說,這個BeanDefinition實際上就是POJO物件在IOC容器中的抽象,通過這個BeanDefinition定義的資料結構,使IOC容器能夠方便地對POJO物件也就是Bean進行管理。
-
註冊BeanDefinition
第三個過程是向IOC容器註冊這些BeanDefinition的過程,這個過程是通過呼叫BeanDefinitionRegistry介面的實現來完成的。這個註冊過程把載入過程中解析得到的BeanDefinition向IOC容器進行註冊,通過分析,我們可以看到,在IOC容器內部將BeanDefinition注入到一個ConcurrentHashMap中去,IOC容器就是通過這個HashMap來持有這些BeanDefinition資料的。
BeanDefinition的Resource定位
以程式設計的方式使用DefaultListableBeanFactory時,首先定義一個Resource來定位容器使用的BeanDefinition。這時使用的是ClassPathResource,這意味著Spring會在類路徑中去尋找以檔案形式存在的BeanDefinition資訊。
ClassPathResource resource = new ClassPathResource("spring.xml");
這裡定義的Resource 並不能由DefaultListableBeanFactory直接使用,Spring通過BeanDefinitionReader來對這些資訊進行處理。在這裡,我們也可以看到使用ApplicationContext相對於直接使用DefaultListableBeanFactory的好處。因為ApplicationContext 中,Spring已經為我們提供了一系列載入不同Resource的讀取器的實現,而DefaultListableBeanFactory只是一個純粹的IOC容器,需要為它配置特定的讀取器才能完成這些功能。當然,有利就有弊,使用DefaultListableBeanFactory 這種更底層的容器,能提高定製IOC容器的靈活性。
下面以ClassPathXmlApplicationContext 為例,通過分析這個ApplicationContext的實現來看看它是怎樣完成這個Resource定位過程的。
這個ClassPathXmlApplicationContext 已經通過繼承AbstractApplicationContext具備了ResourceLoader讀入Resource定義的BeanDefinition的能力,因為AbstractApplicationContext的基類是DefaultResourceLOader。
在前面ClassPathXmlApplicationContext 的原始碼中,我們知道核心程式碼在refresh
方法裡面。下面是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.
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();
}
}
}
refresh 裡面就是Spring的啟動流程,在 refresh
-> obtainFreshBeanFactory
會建立一個BeanFactory,而obtainFreshBeanFactory
在子類AbstractRefreshableApplicationContext
中實現。
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//載入BeanDefinition,其餘的方法暫時不分析
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
在這個方法中,通過 createBeanFactory 構建一個IOC容器供ApplicationContext 使用。這個IOC容器就是我們前面提到過的DefaultListableBeanFactory,同時,它啟動了loadBeanDefinitions 來載入BeanDefinition,這個過程和前面用程式設計式的方法來使用IOC(XmlBeanFactory)的過程非常類似。
AbstractXmlApplicationContext ->loadBeanDefinitions:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
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);
}
設定BeanDefinitionReader,因為AbstractApplicationContext 繼承DefaultResourceLoader ,因此ResourceLoader 可以設定成this
,繼續跟蹤程式碼,在 AbstractBeanDefinitionReader -> loadBeanDefinitions中:
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
return loadCount;
}
catch (IOException ex) {
//...省略
}
}
else {
//...省略程式碼
}
}
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
通過 resourceLoader來獲取配置資源,而這個resourceLoader 就是 ClassPathXmlApplicationContext
,這個getResources 方法在父類AbstractApplicationContext
中實現
AbstractApplicationContext -> getResources:
public Resource[] getResources(String locationPattern) throws IOException {
return this.resourcePatternResolver.getResources(locationPattern);
}
resourcePatternResolver 在初始化的時候,被設定成了 PathMatchingResourcePatternResolver
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
這樣就可以通過PathMatchingResourcePatternResolver 來獲取資源了,這裡就不分析PathMatchingResourcePatternResolver ,可以參考Spring 統一資源定位策略那篇文章,雖然裡面沒有分析PathMatchingResourcePatternResolver ,但是分析了spring Resource的體系結構,有助於理解。
BeanDefinition的載入和解析
在完成BeanDefinition的Resource定位的分析後,下面來了解整個BeanDefinition資訊的載入過程。對IOC容器來說,這個載入過程,相當於把定義的BeanDefinition在IOC容器中轉化成一個Spring內部表示的資料結構的過程。IOC容器對Bean的管理和依賴注入功能的實現,是通過對其持有的BeanDefinition進行各種相關操作來完成的。這些BeanDefinition資料在IOC容器中通過一個HashMap來保持和維護。
在前面,我們定位資源的時候,展示了AbstractBeanDefinitionReader 中的loadBeanDefinitions 方法,在裡面會呼叫下面的程式碼:
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
但這個方法在AbstractBeanDefinitionReader 類裡是沒有實現的,它是一個介面方法,具體的實現在 XmlBeanDefinitionReader
中,在讀取器中,需要得到代表XML檔案的Resource,因為這個Resource物件封裝了對XML檔案的I/O操作,所以讀取器可以在開啟I/O流後得到XML的檔案物件,有了這個物件檔案以後,就可以按照Spring的Bean定義規則來對這個XML的文件樹進行解析了,這個解析是交給BeanDefinitionParserDelegate來完成的。
XmlBeanDefinitionReader -> loadBeanDefinitions:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
//省略程式碼
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
//省略程式碼
}
finally {
//省略程式碼
}
}
接著看doLoadBeanDefinitions 方法:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//獲取XML檔案的Document物件
Document doc = doLoadDocument(inputSource, resource);
//對BeanDefinition解析的過程
return registerBeanDefinitions(doc, resource);
}
//省略部分程式碼
}
這裡我們就不去分析如何得到Document物件的了,我們關心的是Spring的BeanDefinion是怎麼按照Spring的Bean語義要求進行解析並轉化為容器內部資料機構的,這個過程是在registerBeanDefinitions 中完成的。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//通過 BeanDefinitionDocumentReader 對XML的BeanDefinition 進行解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
//具體解析過程
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
BeanDefinition的載入分成兩部分,首先通過呼叫XML的解析器得到document物件,但這些document物件並沒有按照Spring的Bean規則進行解析。在完成通用的XML解析過後,才是按照Spring的Bean規則進行解析的地方,這個按照Spring的Bean規則進行解析的過程是在documentReader中實現的,這裡使用的documentReader是預設配置好的DefaultBeanDefinitionDocumentReader。
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanUtils.instantiateClass(this.documentReaderClass);
}
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
DefaultBeanDefinitionDocumentReader.class;
在 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的配置的解析處理是通過processBeanDefinition 方法的。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 將 <bean /> 節點中的資訊提取出來,然後封裝到一個 BeanDefinitionHolder 中
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 如果有自定義屬性的話,進行相應的解析,先忽略
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//這裡是向IOC容器註冊BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
//省略程式碼
}
//在BeanDefinition向IOC容器註冊完後,傳送訊息
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
提取出來的資訊結果由BeanDefinitionHolder物件來持有。這個BeanDefinitionHolder除了持有BeanDefinition物件外,還持有其他與BeanDefinition的使用相關的資訊,比如Bean的名字,別名集合等。
public class BeanDefinitionHolder implements BeanMetadataElement {
private final BeanDefinition beanDefinition;
private final String beanName;
private final String[] aliases;
...
具體的Spring BeanDefinition的解析是在BeanDefinitionParserDelegate 中完成的,這個類包含了對各種Spring Bean 定義的規則的處理,這裡我們暫且就不深入了。
BeanDefinition在IOC容器中的註冊
前面已經分析過了BeanDefinition在IOC容器中載入和解析的過程。在這些動作完成以後,使用者定義的BeanDefinition資訊已經在IOC容器內建立起了自己的資料結構以及相應的資料表示,但此時這些資料還不能供IOC容器直接使用,需要在IOC容器中對這些BeanDefinition資料進行註冊,這個註冊為IOC容器提供了更友好的使用方式,在DefaultListableBeanFactory中,是通過一個ConcurrentHashMap來持有載入的BeanDefinition的。
在DefaultBeanDefinitionDocumentReader ->processBeanDefinition 中通過如下程式碼註冊:
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
// registry 就是 DefaultListableBeanFactory
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 如果還有別名的話,也要根據別名全部註冊一遍,不然根據別名就會找不到 Bean 了
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
// alias -> beanName 儲存它們的別名資訊,這個很簡單,用一個 map 儲存一下就可以了,
// 獲取的時候,會先將 alias 轉換為 beanName,然後再查詢
registry.registerAlias(beanName, alias);
}
}
}
檢視 DefaultListableBeanFactory 中的 registerBeanDefinition 方法:
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
//省略了部分程式碼
// 檢查BeanDefinition 是否已經存在
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
//如果不允許覆蓋
if (!isAllowBeanDefinitionOverriding()) {
//拋異常
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
//可參考 BeanFactory 原始碼分析(1)中的BeanDefinition分析
//用框架定義的 Bean 覆蓋使用者自定義的 Bean
}
else if (!beanDefinition.equals(existingDefinition)) {
//用新的 Bean 覆蓋舊的 Bean
}
else {
//用同等的 Bean 覆蓋舊的 Bean,這裡指的是 equals 方法返回 true 的 Bean
}
// 覆蓋
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
// 判斷是否已經有其他的 Bean 開始初始化了.
// 注意,"註冊Bean" 這個動作結束,Bean 依然還沒有初始化
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<String>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// 一般會進到這個分支。
// 將 BeanDefinition 放到這個 map 中,這個 map 儲存了所有的 BeanDefinition
this.beanDefinitionMap.put(beanName, beanDefinition);
// 這是個 ArrayList,所以會按照 bean 配置的順序儲存每一個註冊的 Bean 的名字
this.beanDefinitionNames.add(beanName);
// 這是個 LinkedHashSet,代表的是手動註冊的 singleton bean,
// 注意這裡是 remove 方法,到這裡的 Bean 當然不是手動註冊的
// 手動指的是通過呼叫以下方法註冊的 bean :
// registerSingleton(String beanName, Object singletonObject)
// 這不是重點,Spring 會在後面"手動"註冊一些 Bean,
// 如 "environment"、"systemProperties" 等 bean,我們自己也可以在執行時註冊 Bean 到容器中的
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
}
省略了部分程式碼,這裡主要展示的大致流程,對於具體的細節這裡並沒有涉及。現在我們已經分析完BeanDefinition的載入和註冊了,此時依賴注入並沒有發生,依賴注入發生在應用第一次向容器索要bean時,當然可以設定Bean的lazy-init
屬性來控制預例項化的過程,這裡我們並不會分析依賴注入過程,只是大致梳理了IOC容器的初始化過程,後面會再次深入這部分,一點一點的解剖。
總結
在分析了底層的BeanFactory後,我們分析了高階形態的BeanFactory-ApplicationContext,ApplicationContext其實就是將其他功能集成了起來,使得BeanFactory不僅僅是一個容器了,ApplicationContext具有了下面的特性:
- 支援不同的資訊源
- 訪問資源。
- 支援應用事件。
隨後我們以ClassPathXmlApplicationContext為例簡單的分析了BeanDefinition的資源定位,載入和解析以及註冊過程。
- 通過PathMatchingResourcePatternResolver 來進行定位
- XmlBeanDefinitionReader 讀取XML,再通過 BeanDefinitionDocumentReader 讀取配置資訊,將配置資訊放入 BeanDefinitionHolder 中。
- 在DefaultListableBeanFactory 中註冊這些 BeanDefinition
參考
Spring 技術內幕