1. 程式人生 > >Spring Core Container 源碼分析七:註冊 Bean Definitions

Spring Core Container 源碼分析七:註冊 Bean Definitions

resource ans auto throw 大量 iat 獲得 字母 lba

前言

原本以為,Spring 通過解析 bean 的配置,生成並註冊 bean defintions 的過程不太復雜,比較簡單,不用單獨開辟一篇博文來講述;但是當在分析前面兩個章節有關 @Autowired、@Component、@Service 註解的註入機制的時候,發現,如果沒有對有關 bean defintions 的解析和註冊機制徹底弄明白,則很難弄清楚 annotation 在 Spring 容器中的底層運行機制;所以,本篇博文作者將試圖去弄清楚 Spring 容器內部是如何去解析 bean 配置並生成和註冊 bean definitions 的相關主流程;

備註,本文是作者的原創作品,轉載請註明出處。

bean definition 是什麽?

? bean definitions 是什麽?

其實很簡單,就是 Java 中的 POJO,用來描述 bean 配置中的 element 元素的,比如,我們有如下的一個簡單的配置

beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    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">

   <context:component-scan base-package="org.shangyang" />  

   <bean name="jane" class="org.shangyang.spring.container.Person">
       <property name="name" value="Jane Doe"/>
   </bean>

</beans>  

可以看到,上面有三個 element

  1. <beans/>, root element
  2. <context:component/>, component-scan element
  3. <bean/>, bean element

在配置文件 beans.xml 被 Spring 解析的過程中,每一個 element 將會被解析為一個 bean definition 對象緩存在 Spring 容器中;

? 需要被描述為 bean definitions 的配置對象主要分為如下幾大類,

  1. xml-based,解析 xml beans 的情況;
  2. 使用 @Autowired、@Required 註解註入 beans 的解析情況;
    需要特殊處理並解析的元素 <context:annotation-config/>
  3. 使用 @Component、@Service、@Repository,@Beans 註解註入 beans 的解析情況;
    需要特殊處理並解析的元素 <context:annotation-scan/>

? 最開始我的確是這麽認識 bean definitions 的,但是當我分析完有關 bean definitions 的相關邏輯和源碼以後,對其認識有了升華,參考寫在最後;

源碼分析

最好的分析源碼的方式,就是通過高屋建瓴,逐個擊破的方式;首先通過流程圖獲得它的藍圖(頂層設計圖),然後再根據藍圖上的點逐個擊破;最後才能達到融會貫通,胸有成竹的境界;所以,這裏作者用這樣的方式帶你深入剖析 Spring 容器裏面的核心點,以及相關主流程到底是如何運作的。

測試用例

為了一次性把上述源碼分析所描述有的情況闡述清楚,我們繼續使用 Spring Core Container 源碼分析六:@Service 中使用的測試用例;唯一做的修改是,再使用一個特殊的 element xmlns:p 來配置 john,這樣可以進一步去調試自定義 Spring 配置標簽是如何實現的;

beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    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">

   <context:component-scan base-package="org.shangyang" />  

    <bean name="john"
          class="org.shangyang.spring.container.Person"
          p:name="John Doe"
          p:spouse-ref="jane"/>

    <bean name="jane" class="org.shangyang.spring.container.Person">
        <property name="name" value="Jane Doe"/>
    </bean>

   <bean name="niba" class="org.shangyang.spring.container.Dog">
      <property name="name" value="Niba" />
   </bean>

</beans>

流程分析

整個流程是從解析 bean definitions 流程開始的,對應的入口是主流程的 step 1.1.1.2 obtainFreshBeanFactory

入口流程

技術分享圖片

  1. 首選初始化得到 BeanFactory 實例 DefaultListableBeanFactory,用來註冊解析配置後生成的 bean definitions;

  2. 然後通過 XmlBeanDefinitionReader 解析 Spring XML 配置文件
    根據用戶指定的 XML 文件路徑 location,進行解析並且得到 Resource[] 對象,具體參考 step 1.1.3.3.1.1 getResource(location) 步驟;這裏,對其如何通過 location 得到 Resource[] 對象做進一步分析,看源碼,

    PathMatchingResourcePatternResolver.java

    public Resource[] getResources(String locationPattern) throws IOException {
      Assert.notNull(locationPattern, "Location pattern must not be null");
      if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
         // a class path resource (multiple resources for same name possible)
         if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
            // a class path resource pattern
            return findPathMatchingResources(locationPattern);
         }
         else {
            // all class path resources with the given name
            return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
         }
      }
      else {
         // Only look for a pattern after a prefix here
         // (to not get fooled by a pattern symbol in a strange prefix).
         int prefixEnd = locationPattern.indexOf(":") + 1;
         if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
            // a file pattern
            return findPathMatchingResources(locationPattern);
         }
         else {
            // a single resource with the given name
            return new Resource[] {getResourceLoader().getResource(locationPattern)};
         }
      }
    }   

    這裏的解析過程主要分為兩種情況進行解析,一種是前綴是 classpath: 的情況,一種是普通的情況,正如我們當前所使用的測試用例的情況,既是 new ClassPathXmlApplicationContext("beans.xml") 的情況,這裏不打算在這裏繼續深挖;

  3. 以測試用例 beans.xml 為例,從 #2 解析得到的 Resource 實例 resource 對應的是 beans.xml 的配置信息,從 step 1.1.3.3.1.2 loadBeanDefinitions 開始將會對 resource 既是 beans.xml 中的元素依次進行解析;首先生成對應 beans.xml 的 org.w3c.Document 對象實例 document,見 step 1.1.3.3.1.2.2.1,其次得到解析 document 對象的 BeanDefinitionDocumentReader 實例 documentReader,將當前的 Resource 對象封裝為 XmlReaderContext 實例 xmlReaderContext,最後通過 documentReader 開始正式解析 document 對象得到 bean definitions 並將其註冊到當前的 beanFactory 實例中,該步驟見 step 1.1.3.3.1.2.2.2.3

當完成上述三個步驟以後,將進入 register bean definitions process 流程

register bean definitions process

技術分享圖片

? 首先,重要的兩件事情是,

  • document 對象中獲得了 Root 實例 root,見 step 1.2
    看一個 root 元素,長什麽樣的
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       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">
    </beans>

    就是一個 xml 配置文件中的最頂層元素 <beans/>

  • 然後初始化得到 documentReader 實例的解析對象既 this.delegate<BeanDefinitionParserDelegate>,後面針對 element 元素的解析將會使用到它;

? 後續,當前面的工作準備好了以後,來看看是如何解析 element 的?

首先,判斷 root 元素的 namespace 對應的是不是 default namespace,若不是,將進入 step 1.3.3.3: parse custom element;這裏我們關註常規流程,既是當 root 元素的 namespace 是 default namespace 的流程;

遍歷 root 元素下的所有 element,

  1. 若 element 的 namespace 是 default namespace,將進入 parse default element 流程;
    比如當前 element 是普通的 <bean/>
  2. 若 element 的 namespace 不是 default namespace,將進入 parse custom element 流程;
    比如當前 element 是 <context:annotation-config/> 或者是 <context:component-scan/>

parse default element process

技術分享圖片

可以看到,該流程中包含四個子流程,依次處理不同的 element 元素的情況,其它三種都是比較特殊的情況,我們這裏,主要關註“解析 <bean/>" 元素的流程”

解析 bean element 流程

這裏,為了能夠盡量的展示出解析 <bean/> 元素的流程中的邏輯,我將使用一個比較特殊的 <bean/> 來梳理此部分的流程;

    <bean name="john"
          class="com.example.Person"
          p:name="John Doe"
          p:spouse-ref="jane"/>

<bean/> 元素使用了 namespace xmlns:p="http://www.springframework.org/schema/p"

技術分享圖片

? 首先,通過 BeanDefintionParserDelegate 對象解析該 element,得到一個 BeanDefinitionHolder 對象 bdHolder 實例;該解析過程中會依次去解析 bean id, bean name, 以及相關的 scope, init, autowired model 等等屬性;見 step 1.1

? 其次,對 bean definition 進行相關的修飾操作,見 step 1.2

常規步驟

  1. 遍歷當前 element 中的所有 attributes,依次得到 atttribute node
  2. 取得 node 所對應的 namespace URI,並判斷該 namespace 是否是 custom namespace,如果是 custom namespace,那麽正式進入對該 attribute node 的修飾過程,如下所述;

attribute node 的修飾過程

假設,我們當前的 attribute nodep:spouse-ref="jane",看看該屬性是如何被解析的,

  1. 首先,通過 node namespace 得到對應的 NamespaceHandler 實例 handler
    通過 xmlns:p="http://www.springframework.org/schema/p" 得到的 NamespaceHandler 為 SimplePropertyNamespaceHandler 對象;

  2. 其次,調用 SimplePropertyNamespaceHandler 對象對當前的元素進行解析;
    可以看到,前面的解析並沒有什麽特殊的,從元素 p:spouse-ref="jane" 中解析得到 propery name: spouse-ref,property value: jane;但是後續解析,比較特殊,需要處理 REF_SUFFIX 的情況了,也就是當 property name 的後綴為 -ref 的情況,表示該 attribute 是一個 ref-bean 屬性,其屬性值引用的是其它的 bean 實例,所以呢,這裏將其 property value 封裝為了一個 RuntimeBeanReference 對象實例,表示將來在解析該 property value 為 Java Object 的時候,需要去初始化其引用的 bean 實例 jane,然後註入到當前的 property value 中;

  3. 最後,將解析後得到的 bean definition 封裝在 bean definition holder 對象中進行返回;

? 最後,註冊 bean definition;

step 1.3.2 register.registerBeanDefinition(beanName, beanDefinition)register 就是當前的 bean factory 實例,通過將 bean namebean definition 以鍵值對的方式在當前的 bean factory 中進行註冊;這樣,我們就可以通過 bean 的名字,得到其對應的 bean definition 對象了;

? 寫在該小節最後,

我們也可以自定義某個 element 或者 element attribute,並且定義與之相關的 namespace 和 namespace handler,這樣,就可以使得 Spring 容器解析自定義的元素;類似於 dubbo 配置中所使用的 <dubbo /> 自定義元素那樣;

parse custom element process

此步驟對應 register bean definitions process 步驟中的 step 1.3.3.2

技術分享圖片

該小節我將試圖使用一個常用的 custom element: <context:component-scan/> 來梳理整個流程;

  1. 首先得到與 <context:component-scan /> 元素相關的 namespace uri: http://www.springframework.org/schema/context,見 step 1.1
  2. 通過 #1 得到的 namespace uri 解析得到相應的 NamespaceHandler,這裏得到的是 ContextNamespaceHandler;見 step 1.2
    step 1.2.1 getHandlerMappings() 返回了所有內置的 namespace uri 與 namespace handler 所一一對應的鍵值對;
  3. 使用 #2 返回的 NamespaceHandler 既 ContextNamespaceHandler 進行 parse 操作,見 step 1.3,參考子流程 parse element by ContextNamespaceHandler,註意,之所以這裏單獨使用一個子流程來介紹,是因為使用 ContextNamespaceHandler 來解析只是其中的一種解析情況,將來考慮分析更多的子流程情況;
parse element by ContextNamespaceHandler

繼續 parse custom element process 章節中所使用到的例子,<context:component-scan/> 來分析該流程,

技術分享圖片

? 在開始分析之前,看看 component-scan 元素長什麽樣,
技術分享圖片

註意,component-scan element 本身包含 annotation-config attribute;

? 流程分析

首先,根據 element name: component-scan 找到對應的 BeanDefinitionParser,在 ContextNamespaceHandler 初始化的時候,便初始化設置好 8 對內置的 element nameparsers 的鍵值對;這裏,根據名字 component-scan 找到對應的 parser ComponentScanBeanDefinitionParser 對象;

其次,使用 ComponentScanBeanDefinitionParser 對象開始解析工作,

  1. 首先,解析 <context:component-scan base-package="org.shangyang"/> 得到 basePcakges String[] 對象;

  2. 其次,初始化得到 ClassPathBeanDefinitiionScanner 對象實例 scanner,然後調用 scanner.doScan 方法進入 [do scan 流程](#do-scan 流程),該流程中將會遍歷 base package 中所包含的所有 .class 文件,解析之,並生成相應的 bean definitions;另外在這個流程中,還要註意的是,最後會將 bean definitions 在當前的 bean factory 對象中進行註冊;

  3. 最後,這一步是從 step 1.2.4 開始,主要處理的邏輯為,當 element 含有 annotation-config 屬性的時候,將會註冊一系列的 post-processors-bean-definitions;
do scan 流程

這裏主要介紹上一個小節中 #2 步驟中所提到的 do scan 流程步驟,對應 parse element by ContextNamespaceHandler 流程圖中的 step 1.2.3 scanner.doScan

? 先來看看 step 1.2.3.1 findCandidateComponent(basePackage)

ClassPathScanningCandidateComponentProvider.java (已刪除大量不相幹代碼)

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
   Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
   try {
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + ‘/‘ + this.resourcePattern;

      //1. 從當前用戶自定的 classpath 子路徑中,通過 regex 查詢到所有的所匹配的 resources;要特別註意的是,
      //   這裏為什麽不直接通過 Class Loader 去獲取 classes 來進行判斷? 因為這樣的話就相當於是加載了 Class Type,而 Class Type 的加載過程是通過 Spring 容器嚴格控制的,是不允許隨隨便便加載的
      //   所以,取而代之,使用一個 File Resource 去讀取相關的字節碼,從字節碼中去解析........
      Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); 
      boolean traceEnabled = logger.isTraceEnabled();
      boolean debugEnabled = logger.isDebugEnabled();

      //2. 依次遍歷用戶定義的 bean Class 對象
      for (Resource resource : resources) {
         if (traceEnabled) {
            logger.trace("Scanning " + resource);
         }

         if (resource.isReadable()) {
            try {
               // 將從字節碼中獲取到的相關 annotation(@Service) 以及 FileSystemResource 對象保存在 metadataReader 當中; 
               MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); 
               if (isCandidateComponent(metadataReader)) {
                  ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                  sbd.setResource(resource);
                  sbd.setSource(resource);
                  if (isCandidateComponent(sbd)) {
                     candidates.add(sbd);
                  }
                  ...
               }
               ...
            }
            ...
         }
      }
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
   }
   return candidates;
}
  1. 代碼第 10 行

    Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); 

    這一步通過遞歸搜索 base package 目錄下的所有 .class 文件,並將其字節碼封裝成 Resource[] 對象;上面的註釋解釋得非常清楚了,這裏封裝的是 .class 文件的字節碼,而非 class type;除了註解中所描述的,這裏再引申說明下,這裏為什麽不直接加載其 Class Type 還有一個原因就是當 Spring 在加載 Class Type 的時候,很有可能在該 Class Type 上配置了 AOP,通過 ASM 字節碼技術去修改原有的字節碼以後,再加入 Class Loader 中;所以,之類不能直接去解析 Class Type,而只能通過字節碼的方式去解析;

    這一步同樣告誡我們,在使用 Spring 容器來開發應用的時候,開發者不要隨隨便便的自行加載 Class Type 到容器中,因為有可能在加載 Class Type 之前需要通過 Spring 容器的 ASM AOP 進行字節碼的修改以後再加載;

  2. 代碼第 23 行

    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);

    解析當前的 .class 字節碼,解析出對應的 annotation,比如 @Service,並將其協同 FileSystemResource 對象一同保存到 metadataReader 對象中;

  3. 代碼第 24 行

    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
      for (TypeFilter tf : this.excludeFilters) {
         if (tf.match(metadataReader, this.metadataReaderFactory)) {
            return false;
         }
      }
      for (TypeFilter tf : this.includeFilters) { // includedFilters 包含三類 annotation,1. @Component 2. @ManagedBean 3. @Named
         if (tf.match(metadataReader, this.metadataReaderFactory)) {
            return isConditionMatch(metadataReader);
         }
      }
      return false;
    }   

    既是從當前的 metadataReader 中去判斷是否存在 1. @Component 2. @ManagedBean 3. @Named 三種註解中的一種,如果是,則進入下面的流程

  4. 代碼 25 - 29 行,將符合 #3 標準的 annotation 封裝為 ScannedGenericBeanDefinition annotation-bean-definition,並加入 candidates 返回
    if (isCandidateComponent(metadataReader)) {
      ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
      sbd.setResource(resource);
      sbd.setSource(resource);
      if (isCandidateComponent(sbd)) {
         candidates.add(sbd);
      }
      ...
    }

? 依次處理並註冊返回的 candidates

該步驟從流程圖 parse element by ContextNamespaceHandler 中的 step 1.2.3.2 開始,主要做了如下幾件事情,

  1. 設置 candiate (既 annotation bean definition) 的 scope
  2. 通過 AnnotationBeanNameGenerator 生成 bean name,因為通過 @Component、@Service 註解的方式註入的 bean 往往沒有配置 bean name,所以往往需要通過程序的方式自行生成相應的 bean name,看看內部的源碼,如何生成 bean name 的,
    /**
    * 因為通過 @Component、@Serivce 等註解的方式不會像 xml-based 配置那樣提供了一個 name 的標簽,可以指定 bean name;所以,這裏需要去單獨為其生成一個;
    */
    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
      if (definition instanceof AnnotatedBeanDefinition) {
         String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); // 處理諸如 @Service("dogService") 的情況
         if (StringUtils.hasText(beanName)) {
            // Explicit bean name found.
            return beanName;
         }
      }
      // Fallback: generate a unique default bean name. 裏面的實現邏輯就是通過將 Class Name 的首字母大寫編程小寫,然後返回;
      return buildDefaultBeanName(definition, registry);
    }   

    通常情況下,是將類名的首字母進行小寫並返回;對應 step 1.2.2.3.3

  3. 設置 annotation bean definition 的默認值,參考 step 1.2.4
  4. 設置 scoped proxy 到當前的 annotation bean definition
  5. 最後,將 annotation bean definition 註冊到當前的 bean factory
註冊 post-processor-bean-definitions

該步驟從流程圖 parse element by ContextNamespaceHandler 的 step 1.2.4.2 registerAnnotationConfigProcessors 開始,將會依次註冊由如下 post-processor class 對象所對應的 post-processor-bean-definitions,

  • ConfigurationClassPostProcessor.class
  • AutowiredAnnotationBeanPostProcessor.class
  • RequiredAnnotationBeanPostProcessor.class
  • CommonAnnotationBeanPostProcessor.class
  • 通過 PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME 發射得到的 class
  • EventListenerMethodProcessor.class
  • DefaultEventListenerFactory.class

註意,這裏都是通過 Class 對象註冊的,並非註冊的實例化對象,下面,我們來簡單分析一下註冊相關的源碼,以註冊 AutowiredAnnotationBeanPostProcessor post-processor-bean-definition 為例子,

AnnotationConfigUtils#registerAnnotationConfigProcessors

if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
  // 將 AutowiredAnnotationBeanPostProcessor.class 封裝為 bean definition
  RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
  def.setSource(source);
  beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}

上面的步驟將 AutowiredAnnotationBeanPostProcessor.class 封裝為 bean definition;

AnnotationConfigUtils.registerPostProcessor

private static BeanDefinitionHolder registerPostProcessor(
     BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {

  definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  registry.registerBeanDefinition(beanName, definition); // 註冊 bean definition
  return new BeanDefinitionHolder(definition, beanName);
}

這一步將 AutowiredAnnotationBeanPostProcessor 所對應的 bean definition 註入了當前的 bean factory 當中;

AutowiredAnnotationBeanPostProcessor 提供了 @Autowired 註解註入機制的實現,詳情參考 AutowiredAnnotationBeanPostProcessor 章節;

寫在最後

通過上述的分析,可以清晰的看到,bean definition 的作用是什麽,就是通過 bean definition 中的描述去限定通過 Class Type 實例化得到 instance 的業務規則,我們看看由 do scan 流程 所生成的 annotation-bean-definition<ScannedGenericBeanDefinition> 對象,

{% asset_img debug-scanned-generic-bean-definition.png %}

可以看到,當我們在後續要根據該 annotation-bean-definition 得到一個 DogService 實例的時候,所要遵循的業務規則,如下所示,

Generic bean: class [org.shangyang.spring.container.DogService]; 
scope=; 
abstract=false; 
lazyInit=false; 
autowireMode=0; 
dependencyCheck=0; 
autowireCandidate=true; 
primary=false; 
factoryBeanName=null; 
factoryMethodName=null; 
initMethodName=null; 
destroyMethodName=null; 
defined in file [/Users/mac/workspace/spring/framework/sourcecode-analysis/spring-core-container/spring-sourcecode-test/target/classes/org/shangyang/spring/container/DogService.class]

不過,要註意,這裏所得到的 ScannedGenericBeanDefinition 實例,同樣沒有真正去加載 org.shangyang.spring.container.DogService Class Type 到容器中,而只是將 class name 字符串賦值給了 ScannedGenericBeanDefinition.beanClass,言外之意,將來在加載 Class Type 到容器中的時候,或許與實例化 instance 一樣也要根據 bean definitions 中的規則來限定其加載行為,目前我所能夠想到的與其相關的就是 ASM 字節碼技術,可以在 bean definition 中定義 ASM 字節碼修改規則,來控制相關 Class Type 的加載行為;

References

本文轉載自本人的私人博客,傷神的博客 http://www.shangyang.me/2017/04/07/spring-core-container-sourcecode-analysis-register-bean-definitions/

Spring Core Container 源碼分析七:註冊 Bean Definitions