Spring IOC 容器源碼分析(一)

Spring 最重要的概念是 IOC 和 AOP,本篇文章其實就是要帶領(lǐng)大家來分析下 Spring 的 IOC 容器。既然大家平時都要用到 Spring,怎么可以不好好了解 Spring 呢九妈?閱讀本文并不能讓你成為 Spring 專家,不過一定有助于大家理解 Spring 的很多概念雾鬼,幫助大家排查應(yīng)用中和 Spring 相關(guān)的一些問題萌朱。

本文采用的源碼版本是 4.3.11.RELEASE,算是 5.0.x 前比較新的版本了策菜。為了降低難度晶疼,本文所說的所有的內(nèi)容都是基于 xml 的配置的方式酒贬,實際使用已經(jīng)很少人這么做了,至少不是純 xml 配置冒晰,不過從理解源碼的角度來看用這種方式來說無疑是最合適的同衣。

閱讀建議:讀者至少需要知道怎么配置 Spring,了解 Spring 中的各種概念壶运,少部分內(nèi)容我還假設(shè)讀者使用過 SpringMVC耐齐。本文要說的 IOC 總體來說有兩處地方最重要,一個是創(chuàng)建 Bean 容器蒋情,一個是初始化 Bean埠况,如果讀者覺得一次性看完本文壓力有點大,那么可以按這個思路分兩次消化棵癣。讀者不一定對 Spring 容器的源碼感興趣辕翰,也許附錄部分介紹的知識對讀者有些許作用。

希望通過本文可以讓讀者不懼怕閱讀 Spring 源碼狈谊,也希望大家能反饋表述錯誤或不合理的地方喜命。

目錄

引言

先看下最基本的啟動 Spring 容器的例子:

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationfile.xml");
}

以上代碼就可以利用配置文件來啟動一個 Spring 容器了,請使用 maven 的小伙伴直接在 dependencies 中加上以下依賴即可河劝,個人比較反對那些不知道要添加什么依賴壁榕,然后把 Spring 的所有相關(guān)的東西都加進來的方式。

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.11.RELEASE</version>
</dependency>

spring-context 會自動將 spring-core赎瞎、spring-beans牌里、spring-aop、spring-expression 這幾個基礎(chǔ) jar 包帶進來务甥。

多說一句牡辽,很多開發(fā)者入門就直接接觸的 SpringMVC,對 Spring 其實不是很了解敞临,Spring 是漸進式的工具态辛,并不具有很強的侵入性,它的模塊也劃分得很合理挺尿,即使你的應(yīng)用不是 web 應(yīng)用奏黑,或者之前完全沒有使用到 Spring,而你就想用 Spring 的依賴注入這個功能票髓,其實完全是可以的攀涵,它的引入不會對其他的組件產(chǎn)生沖突铣耘。

廢話說完洽沟,我們繼續(xù)。ApplicationContext context = new ClassPathXmlApplicationContext(...) 其實很好理解蜗细,從名字上就可以猜出一二裆操,就是在 ClassPath 中尋找 xml 配置文件怒详,根據(jù) xml 文件內(nèi)容來構(gòu)建 ApplicationContext。當(dāng)然踪区,除了 ClassPathXmlApplicationContext 以外昆烁,我們也還有其他構(gòu)建 ApplicationContext 的方案可供選擇,我們先來看看大體的繼承結(jié)構(gòu)是怎么樣的:

1

讀者可以大致看一下類名缎岗,源碼分析的時候不至于找不著看哪個類静尼,因為 Spring 為了適應(yīng)各種使用場景,提供的各個接口都可能有很多的實現(xiàn)類传泊。對于我們來說鼠渺,就是揪著一個完整的分支看完。

當(dāng)然眷细,讀本文的時候讀者也不必太擔(dān)心拦盹,每個代碼塊分析的時候,我都會告訴讀者我們在說哪個類第幾行溪椎。

我們可以看到普舆,ClassPathXmlApplicationContext 兜兜轉(zhuǎn)轉(zhuǎn)了好久才到 ApplicationContext 接口,同樣的校读,我們也可以使用綠顏色的 FileSystemXmlApplicationContextAnnotationConfigApplicationContext 這兩個類沼侣。

FileSystemXmlApplicationContext 的構(gòu)造函數(shù)需要一個 xml 配置文件在系統(tǒng)中的路徑,其他和 ClassPathXmlApplicationContext 基本上一樣地熄。

AnnotationConfigApplicationContext 是基于注解來使用的华临,它不需要配置文件,采用 java 配置類和各種注解來配置端考,是比較簡單的方式雅潭,也是大勢所趨吧。

不過本文旨在幫助大家理解整個構(gòu)建流程却特,所以決定使用 ClassPathXmlApplicationContext 進行分析扶供。

我們先來一個簡單的例子來看看怎么實例化 ApplicationContext。

首先裂明,定義一個接口:

public interface MessageService {
    String getMessage();
}

定義接口實現(xiàn)類:

public class MessageServiceImpl implements MessageService {

    public String getMessage() {
        return "hello world";
    }
}

接下來椿浓,我們在 resources 目錄新建一個配置文件,文件名隨意闽晦,通常叫 application.xml 或 application-xxx.xml 就可以了:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName">

    <bean id="messageService" class="com.javadoop.example.MessageServiceImpl"/>
</beans>

這樣扳碍,我們就可以跑起來了:

public class App {
    public static void main(String[] args) {
        // 用我們的配置文件來啟動一個 ApplicationContext
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
      
        System.out.println("context 啟動成功");
      
        // 從 context 中取出我們的 Bean,而不是用 new MessageServiceImpl() 這種方式
        MessageService messageService = context.getBean(MessageService.class);
        // 這句將輸出: hello world
        System.out.println(messageService.getMessage());
    }
}

以上例子很簡單仙蛉,不過也夠引出本文的主題了笋敞,就是怎么樣通過配置文件來啟動 Spring 的 ApplicationContext?也就是我們今天要分析的 IOC 的核心了荠瘪。ApplicationContext 啟動過程中夯巷,會負責(zé)創(chuàng)建實例 Bean赛惩,往各個 Bean 中注入依賴等。

BeanFactory 簡介

BeanFactory趁餐,從名字上也很好理解喷兼,生產(chǎn) bean 的工廠,它負責(zé)生產(chǎn)和管理各個 bean 實例后雷。

初學(xué)者可別以為我之前說那么多和 BeanFactory 無關(guān)季惯,前面說的 ApplicationContext 其實就是一個 BeanFactory。我們來看下和 BeanFactory 接口相關(guān)的主要的繼承結(jié)構(gòu):

2

我想臀突,大家看完這個圖以后星瘾,可能就不是很開心了。ApplicationContext 往下的繼承結(jié)構(gòu)前面一張圖說過了惧辈,這里就不重復(fù)了琳状。這張圖呢,背下來肯定是不需要的盒齿,有幾個重點和大家說明下就好念逞。

  1. ApplicationContext 繼承了 ListableBeanFactory,這個 Listable 的意思就是边翁,通過這個接口翎承,我們可以獲取多個 Bean,大家看源碼會發(fā)現(xiàn)符匾,最頂層 BeanFactory 接口的方法都是獲取單個 Bean 的叨咖。
  2. ApplicationContext 繼承了 HierarchicalBeanFactory童漩,Hierarchical 單詞本身已經(jīng)能說明問題了楞泼,也就是說我們可以在應(yīng)用中起多個 BeanFactory,然后可以將各個 BeanFactory 設(shè)置為父子關(guān)系芬沉。
  3. AutowireCapableBeanFactory 這個名字中的 Autowire 大家都非常熟悉焰坪,它就是用來自動裝配 Bean 用的趣倾,但是仔細看上圖,ApplicationContext 并沒有繼承它某饰,不過不用擔(dān)心儒恋,不使用繼承,不代表不可以使用組合黔漂,如果你看到 ApplicationContext 接口定義中的最后一個方法 getAutowireCapableBeanFactory() 就知道了诫尽。
  4. ConfigurableListableBeanFactory 也是一個特殊的接口,看圖炬守,特殊之處在于它繼承了第二層所有的三個接口牧嫉,而 ApplicationContext 沒有。這點之后會用到劳较。
  5. 請先不用花時間在其他的接口和類上驹止,先理解我說的這幾點就可以了。

然后观蜗,請讀者打開編輯器臊恋,翻一下 BeanFactory、ListableBeanFactory墓捻、HierarchicalBeanFactory抖仅、AutowireCapableBeanFactory、ApplicationContext 這幾個接口的代碼砖第,大概看一下各個接口中的方法撤卢,大家心里要有底,限于篇幅梧兼,我就不貼代碼介紹了放吩。

啟動過程分析

下面將會是冗長的代碼分析,記住羽杰,一定要自己打開源碼來看渡紫,不然純看是很累的。

第一步考赛,我們肯定要從 ClassPathXmlApplicationContext 的構(gòu)造方法說起惕澎。

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
  private Resource[] configResources;
  
  // 如果已經(jīng)有 ApplicationContext 并需要配置成父子關(guān)系,那么調(diào)用這個構(gòu)造方法
  public ClassPathXmlApplicationContext(ApplicationContext parent) {
    super(parent);
  }
  ...
  public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
      throws BeansException {

    super(parent);
    // 根據(jù)提供的路徑颜骤,處理成配置文件數(shù)組(以分號唧喉、逗號、空格忍抽、tab八孝、換行符分割)
    setConfigLocations(configLocations);
    if (refresh) {
      refresh(); // 核心方法
    }
  }
    ...
}

接下來,就是 refresh()鸠项,這里簡單說下為什么是 refresh()唆阿,而不是 init() 這種名字的方法。因為 ApplicationContext 建立起來以后锈锤,其實我們是可以通過調(diào)用 refresh() 這個方法重建的驯鳖,refresh() 會將原來的 ApplicationContext 銷毀,然后再重新執(zhí)行一次初始化操作久免。

往下看浅辙,refresh() 方法里面調(diào)用了那么多方法,就知道肯定不簡單了阎姥,請讀者先看個大概记舆,細節(jié)之后會詳細說。

@Override
public void refresh() throws BeansException, IllegalStateException {
   // 來個鎖呼巴,不然 refresh() 還沒結(jié)束泽腮,你又來個啟動或銷毀容器的操作御蒲,那不就亂套了嘛
   synchronized (this.startupShutdownMonitor) {

      // 準備工作,記錄下容器的啟動時間诊赊、標(biāo)記“已啟動”狀態(tài)厚满、處理配置文件中的占位符
      prepareRefresh();
     
      // 這步比較關(guān)鍵,這步完成后碧磅,配置文件就會解析成一個個 Bean 定義碘箍,注冊到 BeanFactory 中,
      // 當(dāng)然鲸郊,這里說的 Bean 還沒有初始化丰榴,只是配置信息都提取出來了,
      // 注冊也只是將這些信息都保存到了注冊中心(說到底核心是一個 beanName-> beanDefinition 的 map)
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 設(shè)置 BeanFactory 的類加載器秆撮,添加幾個 BeanPostProcessor四濒,手動注冊幾個特殊的 bean
      // 這塊待會會展開說
      prepareBeanFactory(beanFactory);

      try {
         // 【這里需要知道 BeanFactoryPostProcessor 這個知識點,Bean 如果實現(xiàn)了此接口职辨,
         // 那么在容器初始化以后峻黍,Spring 會負責(zé)調(diào)用里面的 postProcessBeanFactory 方法〔Υ遥】
        
         // 這里是提供給子類的擴展點姆涩,到這里的時候,所有的 Bean 都加載惭每、注冊完成了骨饿,但是都還沒有初始化
         // 具體的子類可以在這步的時候添加一些特殊的 BeanFactoryPostProcessor 的實現(xiàn)類或做點什么事
         postProcessBeanFactory(beanFactory);
         // 調(diào)用 BeanFactoryPostProcessor 各個實現(xiàn)類的 postProcessBeanFactory(factory) 方法
         invokeBeanFactoryPostProcessors(beanFactory);

         // 注冊 BeanPostProcessor 的實現(xiàn)類,注意看和 BeanFactoryPostProcessor 的區(qū)別
         // 此接口兩個方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
         // 兩個方法分別在 Bean 初始化之前和初始化之后得到執(zhí)行台腥。注意宏赘,到這里 Bean 還沒初始化
         registerBeanPostProcessors(beanFactory);

         // 初始化當(dāng)前 ApplicationContext 的 MessageSource,國際化這里就不展開說了黎侈,不然沒完沒了了
         initMessageSource();

         // 初始化當(dāng)前 ApplicationContext 的事件廣播器察署,這里也不展開了
         initApplicationEventMulticaster();

         // 從方法名就可以知道,典型的模板方法(鉤子方法)峻汉,
         // 具體的子類可以在這里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
         onRefresh();

         // 注冊事件監(jiān)聽器贴汪,監(jiān)聽器需要實現(xiàn) ApplicationListener 接口。這也不是我們的重點休吠,過
         registerListeners();

         // 重點扳埂,重點,重點
         // 初始化所有的 singleton beans
         //(lazy-init 的除外)
         finishBeanFactoryInitialization(beanFactory);

         // 最后瘤礁,廣播事件阳懂,ApplicationContext 初始化完成
         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.
         // 銷毀已經(jīng)初始化的 singleton 的 Beans,以免有些 bean 會一直占用資源
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // 把異常往外拋
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

下面,我們開始一步步來肢解這個 refresh() 方法岩调。

創(chuàng)建 Bean 容器前的準備工作

這個比較簡單巷燥,直接看代碼中的幾個注釋即可。

protected void prepareRefresh() {
   // 記錄啟動時間号枕,
   // 將 active 屬性設(shè)置為 true缰揪,closed 屬性設(shè)置為 false,它們都是 AtomicBoolean 類型
   this.startupDate = System.currentTimeMillis();
   this.closed.set(false);
   this.active.set(true);

   if (logger.isInfoEnabled()) {
      logger.info("Refreshing " + this);
   }

   // Initialize any placeholder property sources in the context environment
   initPropertySources();

   // 校驗 xml 配置文件
   getEnvironment().validateRequiredProperties();

   this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}

創(chuàng)建 Bean 容器堕澄,加載并注冊 Bean

我們回到 refresh() 方法中的下一行 obtainFreshBeanFactory()。

注意霉咨,這個方法是全文最重要的部分之一蛙紫,這里將會初始化 BeanFactory、加載 Bean途戒、注冊 Bean 等等坑傅。

當(dāng)然,這步結(jié)束后喷斋,Bean 并沒有完成初始化唁毒。這里指的是 Bean 實例并未在這一步生成。

// AbstractApplicationContext.java

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
   // 關(guān)閉舊的 BeanFactory (如果有)星爪,創(chuàng)建新的 BeanFactory浆西,加載 Bean 定義、注冊 Bean 等等
   refreshBeanFactory();
  
   // 返回剛剛創(chuàng)建的 BeanFactory
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   if (logger.isDebugEnabled()) {
      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
   }
   return beanFactory;
}

// AbstractRefreshableApplicationContext.java 120

@Override
protected final void refreshBeanFactory() throws BeansException {
   // 如果 ApplicationContext 中已經(jīng)加載過 BeanFactory 了顽腾,銷毀所有 Bean近零,關(guān)閉 BeanFactory
   // 注意,應(yīng)用中 BeanFactory 本來就是可以多個的抄肖,這里可不是說應(yīng)用全局是否有 BeanFactory久信,而是當(dāng)前
   // ApplicationContext 是否有 BeanFactory
   if (hasBeanFactory()) {
      destroyBeans();
      closeBeanFactory();
   }
   try {
      // 初始化一個 DefaultListableBeanFactory,為什么用這個漓摩,我們馬上說裙士。
      DefaultListableBeanFactory beanFactory = createBeanFactory();
      // 用于 BeanFactory 的序列化,我想不部分人應(yīng)該都用不到
      beanFactory.setSerializationId(getId());
     
      // 下面這兩個方法很重要管毙,別跟丟了腿椎,具體細節(jié)之后說
      // 設(shè)置 BeanFactory 的兩個配置屬性:是否允許 Bean 覆蓋、是否允許循環(huán)引用
      customizeBeanFactory(beanFactory);
     
      // 加載 Bean 到 BeanFactory 中
      loadBeanDefinitions(beanFactory);
      synchronized (this.beanFactoryMonitor) {
         this.beanFactory = beanFactory;
      }
   }
   catch (IOException ex) {
      throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
   }
}

看到這里的時候夭咬,我覺得讀者就應(yīng)該站在高處看 ApplicationContext 了酥诽,ApplicationContext 繼承自 BeanFactory,但是它不應(yīng)該被理解為 BeanFactory 的實現(xiàn)類皱埠,而是說其內(nèi)部持有一個實例化的 BeanFactory(DefaultListableBeanFactory)肮帐。以后所有的 BeanFactory 相關(guān)的操作其實是委托給這個實例來處理的。

我們說說為什么選擇實例化 DefaultListableBeanFactory ?前面我們說了有個很重要的接口 ConfigurableListableBeanFactory训枢,它實現(xiàn)了 BeanFactory 下面一層的所有三個接口托修,我把之前的繼承圖再拿過來大家再仔細看一下:

3

我們可以看到 ConfigurableListableBeanFactory 只有一個實現(xiàn)類 DefaultListableBeanFactory,而且實現(xiàn)類 DefaultListableBeanFactory 還通過實現(xiàn)右邊的 AbstractAutowireCapableBeanFactory 通吃了右路恒界。所以結(jié)論就是睦刃,最底下這個家伙 DefaultListableBeanFactory 基本上是最牛的 BeanFactory 了,這也是為什么這邊會使用這個類來實例化的原因十酣。

如果你想要在程序運行的時候動態(tài)往 Spring IOC 容器注冊新的 bean涩拙,就會使用到這個類。那我們怎么在運行時獲得這個實例呢耸采?

之前我們說過 ApplicationContext 接口能獲取到 AutowireCapableBeanFactory兴泥,就是最右上角那個,然后它向下轉(zhuǎn)型就能得到 DefaultListableBeanFactory 了虾宇。

在繼續(xù)往下之前搓彻,我們需要先了解 BeanDefinition。我們說 BeanFactory 是 Bean 容器嘱朽,那么 Bean 又是什么呢旭贬?

這里的 BeanDefinition 就是我們所說的 Spring 的 Bean,我們自己定義的各個 Bean 其實會轉(zhuǎn)換成一個個 BeanDefinition 存在于 Spring 的 BeanFactory 中搪泳。

所以稀轨,如果有人問你 Bean 是什么的時候,你要知道 Bean 在代碼層面上可以認為是 BeanDefinition 的實例岸军。

BeanDefinition 中保存了我們的 Bean 信息靶端,比如這個 Bean 指向的是哪個類、是否是單例的凛膏、是否懶加載杨名、這個 Bean 依賴了哪些 Bean 等等。

BeanDefinition 接口定義

我們來看下 BeanDefinition 的接口定義:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

   // 我們可以看到猖毫,默認只提供 sington 和 prototype 兩種台谍,
   // 很多讀者可能知道還有 request, session, globalSession, application, websocket 這幾種,
   // 不過吁断,它們屬于基于 web 的擴展趁蕊。
   String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
   String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;

   // 比較不重要,直接跳過吧
   int ROLE_APPLICATION = 0;
   int ROLE_SUPPORT = 1;
   int ROLE_INFRASTRUCTURE = 2;

   // 設(shè)置父 Bean仔役,這里涉及到 bean 繼承掷伙,不是 java 繼承。請參見附錄的詳細介紹
   // 一句話就是:繼承父 Bean 的配置信息而已
   void setParentName(String parentName);
  
   // 獲取父 Bean
   String getParentName();
  
   // 設(shè)置 Bean 的類名稱又兵,將來是要通過反射來生成實例的
   void setBeanClassName(String beanClassName);
   
   // 獲取 Bean 的類名稱
   String getBeanClassName();

 
   // 設(shè)置 bean 的 scope
   void setScope(String scope);

   String getScope();

   // 設(shè)置是否懶加載
   void setLazyInit(boolean lazyInit);
   
   boolean isLazyInit();

   // 設(shè)置該 Bean 依賴的所有的 Bean任柜,注意卒废,這里的依賴不是指屬性依賴(如 @Autowire 標(biāo)記的),
   // 是 depends-on="" 屬性設(shè)置的值宙地。
   void setDependsOn(String... dependsOn);

   // 返回該 Bean 的所有依賴
   String[] getDependsOn();

   // 設(shè)置該 Bean 是否可以注入到其他 Bean 中摔认,只對根據(jù)類型注入有效,
   // 如果根據(jù)名稱注入宅粥,即使這邊設(shè)置了 false参袱,也是可以的
   void setAutowireCandidate(boolean autowireCandidate);

   // 該 Bean 是否可以注入到其他 Bean 中
   boolean isAutowireCandidate();

   // 主要的。同一接口的多個實現(xiàn)秽梅,如果不指定名字的話抹蚀,Spring 會優(yōu)先選擇設(shè)置 primary 為 true 的 bean
   void setPrimary(boolean primary);

   // 是否是 primary 的
   boolean isPrimary();

   // 如果該 Bean 采用工廠方法生成,指定工廠名稱企垦。對工廠不熟悉的讀者环壤,請參加附錄
   // 一句話就是:有些實例不是用反射生成的,而是用工廠模式生成的
   void setFactoryBeanName(String factoryBeanName);
   // 獲取工廠名稱
   String getFactoryBeanName();
   // 指定工廠類中的 工廠方法名稱
   void setFactoryMethodName(String factoryMethodName);
   // 獲取工廠類中的 工廠方法名稱
   String getFactoryMethodName();

   // 構(gòu)造器參數(shù)
   ConstructorArgumentValues getConstructorArgumentValues();

   // Bean 中的屬性值竹观,后面給 bean 注入屬性值的時候會說到
   MutablePropertyValues getPropertyValues();

   // 是否 singleton
   boolean isSingleton();

   // 是否 prototype
   boolean isPrototype();

   // 如果這個 Bean 是被設(shè)置為 abstract镐捧,那么不能實例化潜索,
   // 常用于作為 父bean 用于繼承臭增,其實也很少用......
   boolean isAbstract();

   int getRole();
   String getDescription();
   String getResourceDescription();
   BeanDefinition getOriginatingBeanDefinition();
}

這個 BeanDefinition 其實已經(jīng)包含很多的信息了,暫時不清楚所有的方法對應(yīng)什么東西沒關(guān)系竹习,希望看完本文后讀者可以徹底搞清楚里面的所有東西誊抛。

這里接口雖然那么多,但是沒有類似 getInstance() 這種方法來獲取我們定義的類的實例整陌,真正的我們定義的類生成的實例到哪里去了呢拗窃?別著急,這個要很后面才能講到泌辫。

有了 BeanDefinition 的概念以后随夸,我們再往下看 refreshBeanFactory() 方法中的剩余部分:

customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);

雖然只有兩個方法,但路還很長啊震放。宾毒。。

customizeBeanFactory

customizeBeanFactory(beanFactory) 比較簡單殿遂,就是配置是否允許 BeanDefinition 覆蓋诈铛、是否允許循環(huán)引用。

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
   if (this.allowBeanDefinitionOverriding != null) {
      // 是否允許 Bean 定義覆蓋
      beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   if (this.allowCircularReferences != null) {
      // 是否允許 Bean 間的循環(huán)依賴
      beanFactory.setAllowCircularReferences(this.allowCircularReferences);
   }
}

BeanDefinition 的覆蓋問題可能會有開發(fā)者碰到這個坑墨礁,就是在配置文件中定義 bean 時使用了相同的 id 或 name幢竹,默認情況下,allowBeanDefinitionOverriding 屬性為 null恩静,如果在同一配置文件中重復(fù)了焕毫,會拋錯,但是如果不是同一配置文件中,會發(fā)生覆蓋咬荷。

循環(huán)引用也很好理解:A 依賴 B冠句,而 B 依賴 A⌒移梗或 A 依賴 B懦底,B 依賴 C,而 C 依賴 A罕扎。

默認情況下聚唐,Spring 允許循環(huán)依賴,當(dāng)然如果你在 A 的構(gòu)造方法中依賴 B腔召,在 B 的構(gòu)造方法中依賴 A 是不行的杆查。

至于這兩個屬性怎么配置?我在附錄中進行了介紹臀蛛,尤其對于覆蓋問題亲桦,很多人都希望禁止出現(xiàn) Bean 覆蓋,可是 Spring 默認是不同文件的時候可以覆蓋的浊仆。

之后的源碼中還會出現(xiàn)這兩個屬性客峭,讀者有個印象就可以了。

加載 Bean: loadBeanDefinitions

接下來是最重要的 loadBeanDefinitions(beanFactory) 方法了抡柿,這個方法將根據(jù)配置舔琅,加載各個 Bean,然后放到 BeanFactory 中洲劣。

讀取配置的操作在 XmlBeanDefinitionReader 中备蚓,其負責(zé)加載配置、解析囱稽。

// AbstractXmlApplicationContext.java 80

/** 我們可以看到郊尝,此方法將通過一個 XmlBeanDefinitionReader 實例來加載各個 Bean。*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
   // 給這個 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));

   // 初始化 BeanDefinitionReader战惊,其實這個是提供給子類覆寫的流昏,
   // 我看了一下,沒有類覆寫這個方法样傍,我們姑且當(dāng)做不重要吧
   initBeanDefinitionReader(beanDefinitionReader);
   // 重點來了横缔,繼續(xù)往下
   loadBeanDefinitions(beanDefinitionReader);
}

現(xiàn)在還在這個類中,接下來用剛剛初始化的 Reader 開始來加載 xml 配置衫哥,這塊代碼讀者可以選擇性跳過茎刚,不是很重要。也就是說撤逢,下面這個代碼塊膛锭,讀者可以很輕松地略過粮坞。

// AbstractXmlApplicationContext.java 120

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
   Resource[] configResources = getConfigResources();
   if (configResources != null) {
      // 往下看
      reader.loadBeanDefinitions(configResources);
   }
   String[] configLocations = getConfigLocations();
   if (configLocations != null) {
      // 2
      reader.loadBeanDefinitions(configLocations);
   }
}

// 上面雖然有兩個分支,不過第二個分支很快通過解析路徑轉(zhuǎn)換為 Resource 以后也會進到這里
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
   Assert.notNull(resources, "Resource array must not be null");
   int counter = 0;
   // 注意這里是個 for 循環(huán)初狰,也就是每個文件是一個 resource
   for (Resource resource : resources) {
      // 繼續(xù)往下看
      counter += loadBeanDefinitions(resource);
   }
   // 最后返回 counter莫杈,表示總共加載了多少的 BeanDefinition
   return counter;
}

// XmlBeanDefinitionReader 303
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(new EncodedResource(resource));
}

// XmlBeanDefinitionReader 314
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   Assert.notNull(encodedResource, "EncodedResource must not be null");
   if (logger.isInfoEnabled()) {
      logger.info("Loading XML bean definitions from " + encodedResource.getResource());
   }
   // 用一個 ThreadLocal 來存放配置文件資源
   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
   if (currentResources == null) {
      currentResources = new HashSet<EncodedResource>(4);
      this.resourcesCurrentlyBeingLoaded.set(currentResources);
   }
   if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
   }
   try {
      InputStream inputStream = encodedResource.getResource().getInputStream();
      try {
         InputSource inputSource = new InputSource(inputStream);
         if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
         }
         // 核心部分是這里,往下面看
         return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
      }
      finally {
         inputStream.close();
      }
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(
            "IOException parsing XML document from " + encodedResource.getResource(), ex);
   }
   finally {
      currentResources.remove(encodedResource);
      if (currentResources.isEmpty()) {
         this.resourcesCurrentlyBeingLoaded.remove();
      }
   }
}

// 還在這個文件中奢入,第 388 行
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
   try {
      // 這里就不看了筝闹,將 xml 文件轉(zhuǎn)換為 Document 對象
      Document doc = doLoadDocument(inputSource, resource);
      // 繼續(xù)
      return registerBeanDefinitions(doc, resource);
   }
   catch (...
}
// 還在這個文件中,第 505 行
// 返回值:返回從當(dāng)前配置文件加載了多少數(shù)量的 Bean
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   int countBefore = getRegistry().getBeanDefinitionCount();
   // 這里
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   return getRegistry().getBeanDefinitionCount() - countBefore;
}
// DefaultBeanDefinitionDocumentReader 90
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   logger.debug("Loading bean definitions");
   Element root = doc.getDocumentElement();
   // 從 xml 根節(jié)點開始解析文件
   doRegisterBeanDefinitions(root);
}         

經(jīng)過漫長的鏈路腥光,一個配置文件終于轉(zhuǎn)換為一顆 DOM 樹了关顷,注意,這里指的是其中一個配置文件武福,不是所有的议双,讀者可以看到上面有個 for 循環(huán)的。下面開始從根節(jié)點開始解析:

doRegisterBeanDefinitions:
// DefaultBeanDefinitionDocumentReader 116
protected void doRegisterBeanDefinitions(Element root) {
   // 我們看名字就知道捉片,BeanDefinitionParserDelegate 必定是一個重要的類平痰,它負責(zé)解析 Bean 定義,
   // 這里為什么要定義一個 parent? 看到后面就知道了伍纫,是遞歸問題宗雇,
   // 因為 <beans /> 內(nèi)部是可以定義 <beans /> 的,所以這個方法的 root 其實不一定就是 xml 的根節(jié)點翻斟,也可以是嵌套在里面的 <beans /> 節(jié)點逾礁,從源碼分析的角度说铃,我們當(dāng)做根節(jié)點就好了
   BeanDefinitionParserDelegate parent = this.delegate;
   this.delegate = createDelegate(getReaderContext(), root, parent);

   if (this.delegate.isDefaultNamespace(root)) {
      // 這塊說的是根節(jié)點 <beans ... profile="dev" /> 中的 profile 是否是當(dāng)前環(huán)境需要的访惜,
      // 如果當(dāng)前環(huán)境配置的 profile 不包含此 profile,那就直接 return 了腻扇,不對此 <beans /> 解析
      // 不熟悉 profile 為何物债热,不熟悉怎么配置 profile 讀者的請移步附錄區(qū)
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      if (StringUtils.hasText(profileSpec)) {
         String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
         if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            if (logger.isInfoEnabled()) {
               logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                     "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }

   preProcessXml(root); // 鉤子
   // 往下看
   parseBeanDefinitions(root, this.delegate);
   postProcessXml(root); // 鉤子

   this.delegate = parent;
}

preProcessXml(root) 和 postProcessXml(root) 是給子類用的鉤子方法,鑒于沒有被使用到幼苛,也不是我們的重點窒篱,我們直接跳過。

這里涉及到了 profile 的問題舶沿,對于不了解的讀者墙杯,我在附錄中對 profile 做了簡單的解釋,讀者可以參考一下括荡。

接下來高镐,看核心解析方法 parseBeanDefinitions(root, this.delegate) :

// default namespace 涉及到的就四個標(biāo)簽 <import />、<alias />畸冲、<bean /> 和 <beans />嫉髓,
// 其他的屬于 custom 的
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   if (delegate.isDefaultNamespace(root)) {
      NodeList nl = root.getChildNodes();
      for (int i = 0; i < nl.getLength(); i++) {
         Node node = nl.item(i);
         if (node instanceof Element) {
            Element ele = (Element) node;
            if (delegate.isDefaultNamespace(ele)) {
               // 解析 default namespace 下面的幾個元素
               parseDefaultElement(ele, delegate);
            }
            else {
               // 解析其他 namespace 的元素
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}

從上面的代碼观腊,我們可以看到,對于每個配置來說算行,分別進入到 parseDefaultElement(ele, delegate); 和 delegate.parseCustomElement(ele); 這兩個分支了梧油。

parseDefaultElement(ele, delegate) 代表解析的節(jié)點是 <import /><alias />州邢、<bean />儡陨、<beans /> 這幾個。

這里的四個標(biāo)簽之所以是 default 的量淌,是因為它們是處于這個 namespace 下定義的:

http://www.springframework.org/schema/beans

又到初學(xué)者科普時間迄委,不熟悉 namespace 的讀者請看下面貼出來的 xml,這里的第二行 xmlns 就是咯类少。

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd"
       default-autowire="byName">

而對于其他的標(biāo)簽叙身,將進入到 delegate.parseCustomElement(element) 這個分支。如我們經(jīng)常會使用到的 <mvc />硫狞、<task />信轿、<context /><aop />等残吩。

這些屬于擴展财忽,如果需要使用上面這些 ”非 default“ 標(biāo)簽,那么上面的 xml 頭部的地方也要引入相應(yīng)的 namespace 和 .xsd 文件的路徑泣侮,如下所示即彪。同時代碼中需要提供相應(yīng)的 parser 來解析,如 MvcNamespaceHandler活尊、TaskNamespaceHandler隶校、ContextNamespaceHandler、AopNamespaceHandler 等蛹锰。

假如讀者想分析 <context:property-placeholder location="classpath:xx.properties" /> 的實現(xiàn)原理深胳,就應(yīng)該到 ContextNamespaceHandler 中找答案。

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://www.springframework.org/schema/beans"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:mvc="http://www.springframework.org/schema/mvc"
      xsi:schemaLocation="
           http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/mvc   
           http://www.springframework.org/schema/mvc/spring-mvc.xsd  
       "
      default-autowire="byName">

回過神來铜犬,看看處理 default 標(biāo)簽的方法:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      // 處理 <import /> 標(biāo)簽
      importBeanDefinitionResource(ele);
   }
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      // 處理 <alias /> 標(biāo)簽定義
      // <alias name="fromName" alias="toName"/>
      processAliasRegistration(ele);
   }
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      // 處理 <bean /> 標(biāo)簽定義舞终,這也算是我們的重點吧
      processBeanDefinition(ele, delegate);
   }
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // 如果碰到的是嵌套的 <beans /> 標(biāo)簽,需要遞歸
      doRegisterBeanDefinitions(ele);
   }
}

如果每個標(biāo)簽都說癣猾,那我不吐血敛劝,你們都要吐血了。我們挑我們的重點 <bean /> 標(biāo)簽出來說纷宇。

processBeanDefinition 解析 bean 標(biāo)簽

下面是 processBeanDefinition 解析 <bean /> 標(biāo)簽:

// DefaultBeanDefinitionDocumentReader 298

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   // 將 <bean /> 節(jié)點中的信息提取出來夸盟,然后封裝到一個 BeanDefinitionHolder 中,細節(jié)往下看
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  
   // 下面的幾行先不要看呐粘,跳過先满俗,跳過先转捕,跳過先,后面會繼續(xù)說的
  
   if (bdHolder != null) {
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         // Register the final decorated instance.
         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));
   }
}

繼續(xù)往下看怎么解析之前唆垃,我們先看下 <bean /> 標(biāo)簽中可以定義哪些屬性:

Property
class 類的全限定名
name 可指定 id五芝、name(用逗號、分號辕万、空格分隔)
scope 作用域
constructor arguments 指定構(gòu)造參數(shù)
properties 設(shè)置屬性的值
autowiring mode no(默認值)枢步、byName、byType渐尿、 constructor
lazy-initialization mode 是否懶加載(如果被非懶加載的bean依賴了那么其實也就不能懶加載了)
initialization method bean 屬性設(shè)置完成后醉途,會調(diào)用這個方法
destruction method bean 銷毀后的回調(diào)方法

上面表格中的內(nèi)容我想大家都非常熟悉吧,如果不熟悉砖茸,那就是你不夠了解 Spring 的配置了隘擎。

簡單地說就是像下面這樣子:

<bean id="exampleBean" name="name1, name2, name3" class="com.javadoop.ExampleBean"
      scope="singleton" lazy-init="true" init-method="init" destroy-method="cleanup">
  
    <!-- 可以用下面三種形式指定構(gòu)造參數(shù) -->
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg index="0" value="7500000"/>
  
    <!-- property 的幾種情況 -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

當(dāng)然,除了上面舉例出來的這些凉夯,還有 factory-bean货葬、factory-method、<lockup-method />劲够、<replaced-method />震桶、<meta /><qualifier /> 這幾個征绎,大家是不是熟悉呢蹲姐?自己檢驗一下自己對 Spring 中 bean 的了解程度。

有了以上這些知識以后人柿,我們再繼續(xù)往里看怎么解析 bean 元素柴墩,是怎么轉(zhuǎn)換到 BeanDefinitionHolder 的。

// BeanDefinitionParserDelegate 428

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    return parseBeanDefinitionElement(ele, null);
}

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
   String id = ele.getAttribute(ID_ATTRIBUTE);
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

   List<String> aliases = new ArrayList<String>();
      
   // 將 name 屬性的定義按照 “逗號顷扩、分號拐邪、空格” 切分慰毅,形成一個 別名列表數(shù)組隘截,
   // 當(dāng)然,如果你不定義 name 屬性的話汹胃,就是空的了
   // 我在附錄中簡單介紹了一下 id 和 name 的配置婶芭,大家可以看一眼,有個20秒就可以了
   if (StringUtils.hasLength(nameAttr)) {
      String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      aliases.addAll(Arrays.asList(nameArr));
   }

   String beanName = id;
   // 如果沒有指定id, 那么用別名列表的第一個名字作為beanName
   if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
      beanName = aliases.remove(0);
      if (logger.isDebugEnabled()) {
         logger.debug("No XML 'id' specified - using '" + beanName +
               "' as bean name and " + aliases + " as aliases");
      }
   }

   if (containingBean == null) {
      checkNameUniqueness(beanName, aliases, ele);
   }
  
   // 根據(jù) <bean ...>...</bean> 中的配置創(chuàng)建 BeanDefinition着饥,然后把配置中的信息都設(shè)置到實例中,
   // 細節(jié)后面細說犀农,先知道下面這行結(jié)束后,一個 BeanDefinition 實例就出來了宰掉。
   AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
   
   // 到這里呵哨,整個 <bean /> 標(biāo)簽就算解析結(jié)束了赁濒,一個 BeanDefinition 就形成了。
   if (beanDefinition != null) {
      // 如果都沒有設(shè)置 id 和 name孟害,那么此時的 beanName 就會為 null,進入下面這塊代碼產(chǎn)生
      // 如果讀者不感興趣的話,我覺得不需要關(guān)心這塊代碼肴茄,對本文源碼分析來說剃氧,這些東西不重要
      if (!StringUtils.hasText(beanName)) {
         try {
            if (containingBean != null) {// 按照我們的思路,這里 containingBean 是 null 的
               beanName = BeanDefinitionReaderUtils.generateBeanName(
                     beanDefinition, this.readerContext.getRegistry(), true);
            }
            else {
               // 如果我們不定義 id 和 name谎柄,那么我們引言里的那個例子:
               //   1. beanName 為:com.javadoop.example.MessageServiceImpl#0
               //   2. beanClassName 為:com.javadoop.example.MessageServiceImpl
              
               beanName = this.readerContext.generateBeanName(beanDefinition);
               
               String beanClassName = beanDefinition.getBeanClassName();
               if (beanClassName != null &&
                     beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                     !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                  // 把 beanClassName 設(shè)置為 Bean 的別名
                  aliases.add(beanClassName);
               }
            }
            if (logger.isDebugEnabled()) {
               logger.debug("Neither XML 'id' nor 'name' specified - " +
                     "using generated bean name [" + beanName + "]");
            }
         }
         catch (Exception ex) {
            error(ex.getMessage(), ele);
            return null;
         }
      }
      String[] aliasesArray = StringUtils.toStringArray(aliases);
      // 返回 BeanDefinitionHolder
      return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
   }

   return null;
}

然后丁侄,我們再看看怎么根據(jù)配置創(chuàng)建 BeanDefinition 實例的:

public AbstractBeanDefinition parseBeanDefinitionElement(
      Element ele, String beanName, BeanDefinition containingBean) {

   this.parseState.push(new BeanEntry(beanName));

   String className = null;
   if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
      className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
   }

   try {
      String parent = null;
      if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
         parent = ele.getAttribute(PARENT_ATTRIBUTE);
      }
      // 創(chuàng)建 BeanDefinition,然后設(shè)置類信息而已朝巫,很簡單鸿摇,就不貼代碼了
      AbstractBeanDefinition bd = createBeanDefinition(className, parent);

      // 設(shè)置 BeanDefinition 的一堆屬性,這些屬性定義在 AbstractBeanDefinition 中
      parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
      bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
    
      /**
       * 下面的一堆是解析 <bean>......</bean> 內(nèi)部的子元素劈猿,
       * 解析出來以后的信息都放到 bd 的屬性中
       */
     
      // 解析 <meta />
      parseMetaElements(ele, bd);
      // 解析 <lookup-method />
      parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
      // 解析 <replaced-method />
      parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
    // 解析 <constructor-arg />
      parseConstructorArgElements(ele, bd);
      // 解析 <property />
      parsePropertyElements(ele, bd);
      // 解析 <qualifier />
      parseQualifierElements(ele, bd);

      bd.setResource(this.readerContext.getResource());
      bd.setSource(extractSource(ele));

      return bd;
   }
   catch (ClassNotFoundException ex) {
      error("Bean class [" + className + "] not found", ele, ex);
   }
   catch (NoClassDefFoundError err) {
      error("Class that bean class [" + className + "] depends on not found", ele, err);
   }
   catch (Throwable ex) {
      error("Unexpected failure during bean definition parsing", ele, ex);
   }
   finally {
      this.parseState.pop();
   }

   return null;
}

到這里户辱,我們已經(jīng)完成了根據(jù) <bean /> 配置創(chuàng)建了一個 BeanDefinitionHolder 實例。注意糙臼,是一個庐镐。

我們回到解析 <bean /> 的入口方法:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   // 將 <bean /> 節(jié)點轉(zhuǎn)換為 BeanDefinitionHolder,就是上面說的一堆
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
      // 如果有自定義屬性的話变逃,進行相應(yīng)的解析必逆,先忽略
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         // 我們把這步叫做 注冊Bean 吧
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error("Failed to register bean definition with name '" +
               bdHolder.getBeanName() + "'", ele, ex);
      }
      // 注冊完成后,發(fā)送事件揽乱,本文不展開說這個
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}

大家再仔細看一下這塊吧名眉,我們后面就不回來說這個了。這里已經(jīng)根據(jù)一個 <bean /> 標(biāo)簽產(chǎn)生了一個 BeanDefinitionHolder 的實例凰棉,這個實例里面也就是一個 BeanDefinition 的實例和它的 beanName损拢、aliases 這三個信息,注意撒犀,我們的關(guān)注點始終在 BeanDefinition 上:

public class BeanDefinitionHolder implements BeanMetadataElement {

  private final BeanDefinition beanDefinition;

  private final String beanName;

  private final String[] aliases;
...

然后我們準備注冊這個 BeanDefinition福压,最后,把這個注冊事件發(fā)送出去或舞。

下面荆姆,我們開始說說注冊 Bean 吧。

注冊 Bean

// BeanDefinitionReaderUtils 143

public static void registerBeanDefinition(
      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
      throws BeanDefinitionStoreException {

   String beanName = definitionHolder.getBeanName();
   // 注冊這個 Bean
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

   // 如果還有別名的話映凳,也要根據(jù)別名全部注冊一遍胆筒,不然根據(jù)別名就會找不到 Bean 了
   String[] aliases = definitionHolder.getAliases();
   if (aliases != null) {
      for (String alias : aliases) {
         // alias -> beanName 保存它們的別名信息,這個很簡單诈豌,用一個 map 保存一下就可以了仆救,
         // 獲取的時候抒和,會先將 alias 轉(zhuǎn)換為 beanName,然后再查找
         registry.registerAlias(beanName, alias);
      }
   }
}

別名注冊的放一邊彤蔽,畢竟它很簡單构诚,我們看看怎么注冊 Bean。

// DefaultListableBeanFactory 793

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException {

   Assert.hasText(beanName, "Bean name must not be empty");
   Assert.notNull(beanDefinition, "BeanDefinition must not be null");

   if (beanDefinition instanceof AbstractBeanDefinition) {
      try {
         ((AbstractBeanDefinition) beanDefinition).validate();
      }
      catch (BeanDefinitionValidationException ex) {
         throw new BeanDefinitionStoreException(...);
      }
   }

   // old? 還記得 “允許 bean 覆蓋” 這個配置嗎铆惑?allowBeanDefinitionOverriding
   BeanDefinition oldBeanDefinition;
  
   // 之后會看到范嘱,所有的 Bean 注冊后會放入這個 beanDefinitionMap 中
   oldBeanDefinition = this.beanDefinitionMap.get(beanName);
  
   // 處理重復(fù)名稱的 Bean 定義的情況
   if (oldBeanDefinition != null) {
      if (!isAllowBeanDefinitionOverriding()) {
         // 如果不允許覆蓋的話,拋異常
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription()...
      }
      else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
         // log...用框架定義的 Bean 覆蓋用戶自定義的 Bean 
      }
      else if (!beanDefinition.equals(oldBeanDefinition)) {
         // log...用新的 Bean 覆蓋舊的 Bean
      }
      else {
         // log...用同等的 Bean 覆蓋舊的 Bean员魏,這里指的是 equals 方法返回 true 的 Bean
      }
      // 覆蓋
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
   else {
      // 判斷是否已經(jīng)有其他的 Bean 開始初始化了.
      // 注意丑蛤,"注冊Bean" 這個動作結(jié)束,Bean 依然還沒有初始化撕阎,我們后面會有大篇幅說初始化過程受裹,
      // 在 Spring 容器啟動的最后,會 預(yù)初始化 所有的 singleton beans
      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 {
         // 最正常的應(yīng)該是進到這個分支虏束。
        
         // 將 BeanDefinition 放到這個 map 中棉饶,這個 map 保存了所有的 BeanDefinition
         this.beanDefinitionMap.put(beanName, beanDefinition);
         // 這是個 ArrayList,所以會按照 bean 配置的順序保存每一個注冊的 Bean 的名字
         this.beanDefinitionNames.add(beanName);
         // 這是個 LinkedHashSet镇匀,代表的是手動注冊的 singleton bean照藻,
         // 注意這里是 remove 方法,到這里的 Bean 當(dāng)然不是手動注冊的
         // 手動指的是通過調(diào)用以下方法注冊的 bean :
         //     registerSingleton(String beanName, Object singletonObject)
         // 這不是重點汗侵,解釋只是為了不讓大家疑惑幸缕。Spring 會在后面"手動"注冊一些 Bean,
         // 如 "environment"晰韵、"systemProperties" 等 bean发乔,我們自己也可以在運行時注冊 Bean 到容器中的
         this.manualSingletonNames.remove(beanName);
      }
      // 這個不重要,在預(yù)初始化的時候會用到雪猪,不必管它栏尚。
      this.frozenBeanDefinitionNames = null;
   }

   if (oldBeanDefinition != null || containsSingleton(beanName)) {
      resetBeanDefinition(beanName);
   }
}

總結(jié)一下,到這里已經(jīng)初始化了 Bean 容器只恨,<bean /> 配置也相應(yīng)的轉(zhuǎn)換為了一個個 BeanDefinition译仗,然后注冊了各個 BeanDefinition 到注冊中心,并且發(fā)送了注冊事件坤次。
未完待續(xù)......

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末古劲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缰猴,更是在濱河造成了極大的恐慌,老刑警劉巖疤剑,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滑绒,死亡現(xiàn)場離奇詭異闷堡,居然都是意外死亡,警方通過查閱死者的電腦和手機疑故,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門杠览,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纵势,你說我怎么就攤上這事踱阿。” “怎么了钦铁?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵软舌,是天一觀的道長。 經(jīng)常有香客問我牛曹,道長佛点,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任黎比,我火速辦了婚禮超营,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘阅虫。我一直安慰自己演闭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布颓帝。 她就那樣靜靜地躺著船响,像睡著了一般。 火紅的嫁衣襯著肌膚如雪躲履。 梳的紋絲不亂的頭發(fā)上见间,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音工猜,去河邊找鬼米诉。 笑死,一個胖子當(dāng)著我的面吹牛篷帅,可吹牛的內(nèi)容都是我干的史侣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼魏身,長吁一口氣:“原來是場噩夢啊……” “哼惊橱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起箭昵,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤税朴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體正林,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡泡一,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了觅廓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鼻忠。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖杈绸,靈堂內(nèi)的尸體忽然破棺而出帖蔓,到底是詐尸還是另有隱情,我是刑警寧澤瞳脓,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布塑娇,位于F島的核電站,受9級特大地震影響篡殷,放射性物質(zhì)發(fā)生泄漏钝吮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一板辽、第九天 我趴在偏房一處隱蔽的房頂上張望奇瘦。 院中可真熱鬧,春花似錦劲弦、人聲如沸耳标。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽次坡。三九已至,卻和暖如春画畅,著一層夾襖步出監(jiān)牢的瞬間砸琅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工轴踱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留症脂,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓淫僻,卻偏偏與公主長得像诱篷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子雳灵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360