原始碼學習之Spring容器建立原理

京東雲發表於2022-11-14

1 前言

眾所周知,Spring可以幫我們管理我們需要的bean。在我們需要用到這些bean的時候,可以很方便的獲取到它,然後進行一系列的操作。比如,我們定義一個bean MyTestBean

public class MyTestBean {
    private String testStr = "testStr";

    public String getTestStr() {
        return testStr;
    }

    public void setTestStr(String testStr) {
        this.testStr = testStr;
    }

然後xml配置一下

<?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-3.1.xsd">
   <bean id = "myTestBean" class="bean.MyTestBean"/>
</beans>

編寫一下測試程式碼,測試一下,就會看到測試透過的結果。

public class BeanFactoryTest {

    @Test
    public void testSimpleLoad() {
        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
        MyTestBean bean = (MyTestBean)bf.getBean("myTestBean");
        assert "testStr".equals(bean.getTestStr());
    }
}

直接使用BeanFactory作為容器對於Spring來說不常見,這裡只是用來測試,以便可以更快更好地分析Spring內部原理。其涉及到的一些元件,貫穿整個Spring容器當中,對於我們瞭解其他Spring容器也有很大的幫助。限於篇幅,這裡只介紹該容器建立的部分,即對於new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”)),Spring都幹了些什麼。

2 Spring容器建立原理

2.1 整體實現流程

首先我們大致瞭解一下Spring容器建立的整體過程。

原始碼學習之Spring容器建立原理


整體時序圖


可以看到,該Spring容器建立大致分為以下幾部分:

  1. 資源的封裝,以Resource封裝配置檔案
  2. 載入BeanDefinition
  3. 解析配置檔案,獲取Document
  4. 解析及註冊BeanDefinition
  5. 標籤的解析,分為預設標籤和自定義標籤的解析

下面我們就以這樣的順序對各個部分從程式碼實現上進行具體分析。

2.2 核心類介紹

在進行具體建立邏輯之前,我們先對Spring容器建立的核心類進行介紹,以便我們更好地掌握它的實現過程。

2.2.1 DefaultListableBeanFactory

XmlBeanFactory繼承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整個bean載入的核心部分,是Spring註冊及載入bean的預設實現。XmlBeanFactory與DefaultListableBeanFactory的不同之處在於,XmlBeanFactory使用了自定義的XML讀取器XmlBeanDefinitionReader,實現了個性化的BeanDefinitionReader讀取。

原始碼學習之Spring容器建立原理


容器載入相關類圖

2.2.2 XmlBeanDefinitionReader

XmlBeanDefinitionReader用於資原始檔讀取、解析及Bean註冊。讀取Xml配置檔案的流程大致為,首先使用ResourceLoader將資原始檔路徑轉換為對應的Resource檔案,然後將Resource檔案轉換為Document檔案,最後對Document及Element進行解析。

原始碼學習之Spring容器建立原理


配置檔案讀取相關類圖

2.2.3 BeanDefinition

在Spring中,BeanDefinition是配置檔案元素標籤在容器中的內部表示形式,包含了元素的所有資訊。Spring將配置檔案中的轉換為BeanDefinition,並將這些BeanDefinition註冊到BeanDefinitionRegistry中。BeanDefinitionRegistry以map形式儲存,後續操作直接從BeanDefinitionRegistry中讀取配置資訊。

原始碼學習之Spring容器建立原理


BeanDefinition及其實現類

2.3 配置檔案的封裝

在Java中不同的資源都要抽象成URL,然後使用不同型別的URLStreamHandler處理不同的URL表示的資源。但是,Spring對其內部使用到的資源實現了自己的抽象結構:Resource介面封裝底層資源。主要原因有3點:

  1. URL沒有預設定義相對Classpath或ServletContext等資源的handler
  2. URL沒有提供基本的方法,例如檢查當前資源是否存在是否可讀等
  3. 自定義URL handler需要了解URL實現機制

對不同來源的資原始檔都有相應的Resource實現:檔案(FileSystemResource)、Classpath資源(ClassPathResource)、URL資源(UrlResource)、InputStream資源(InputStreamResource)、Byte陣列(ByteArrayResource)等。

原始碼學習之Spring容器建立原理


資原始檔處理相關類圖

2.4 載入BeanDefinition

下面我們就從程式碼層次看看整個容器究竟是怎麼實現的。觀察測試程式碼,我們可以將XmlBeanFactory的構造方法作為切入點進行分析。

public XmlBeanFactory(Resource resource) throws BeansException {
   this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    // 載入BeanDefinition
    this.reader.loadBeanDefinitions(resource);
}

主要做了兩件事,一是呼叫父類的構造方法,二是載入BeanDefinition。
首先我們先進入父類構造方法,最終進到AbstractAutowireCapableBeanFactory構造方法中。

public AbstractAutowireCapableBeanFactory() {
   super();
   // 忽略BeanNameAware、BeanFactoryAware和BeanClassLoaderAware介面的自動裝配功能
   ignoreDependencyInterface(BeanNameAware.class);
   ignoreDependencyInterface(BeanFactoryAware.class);
   ignoreDependencyInterface(BeanClassLoaderAware.class);
}

主要是ignoreDependencyInterface方法,它的主要功能是忽略給定介面的自動裝配功能。實現上很簡單,就是把這些Class加入到ignoredDependencyInterfaces集合中,ignoredDependencyInterfaces是Set>型別。

再看載入Bean的方法,執行的是XmlBeanDefinitionReader類的loadBeanDefinitions方法。進入方法,可以看到主要就是做了兩件事,一是構造InputSource,這個類全路徑名是org.xml.sax.InputSource,這步的目的就是透過SAX讀取XML檔案事先準備一下InputSource物件。而真正的載入Bean的邏輯在doLoadBeanDefinitions方法中。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   InputStream inputStream = encodedResource.getResource().getInputStream();
   // 構建InputSource,用於解析XML
InputSource inputSource = new InputSource(inputStream);
   if (encodedResource.getEncoding() != null) {
      inputSource.setEncoding(encodedResource.getEncoding());
   }
// 實際載入BeanDefinition的執行邏輯
   return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}

doLoadBeanDefinitions方法首先載入XML檔案,得到Document物件,然後根據Document物件註冊Bean。我們首先看下得到Document物件的過程。

2.5 獲取Document

獲取Document,首先透過getValidationModeForResource獲取XML驗證模式,然後解析得到Document物件。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
   // 首先獲取XML驗證模式,然後SAX方式解析得到Document
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
         getValidationModeForResource(resource), isNamespaceAware());
}

常用的XML驗證模式有兩種:DTD和XSD,實際使用哪種驗證模式在getValidationModeForResource中進行了解析。這個方法判斷是DTD驗證還是XSD驗證,僅僅是判斷一下XML是否包含DOCTYPE字串。

而解析得到Document的方法很簡單,就是透過SAX解析XML文件的套路。這裡不再贅述。

2.6 解析及註冊BeanDefinition

當把檔案轉換為Document後,接下來的提取及註冊bean就是我們的重頭戲了。呼叫了以下方法。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   int countBefore = getRegistry().getBeanDefinitionCount();
// 註冊BeanDefinition
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

在這個方法中很好地應用了單一職責原則,將邏輯處理委託給單一的類進行處理,而這個邏輯處理類就是BeanDefinitionDocumentReader。BeanDefinitionDocumentReader是一個介面,例項化的工作在createBeanDefinitionDocumentReader()中完成,真正的型別是DefaultBeanDefinitionDocumentReader。而它的registerBeanDefinitions方法很簡單,僅僅是先根據Document獲取了root,實際註冊在doRegisterBeanDefinitions方法中。

protected void doRegisterBeanDefinitions(Element root) {
   if (this.delegate.isDefaultNamespace(root)) {
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      if (StringUtils.hasText(profileSpec)) {
         String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        // 如果環境變數不包含指定profile,則流程結束 
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            return;
        }
      }
   }
   preProcessXml(root);
// 解析BeanDefinition
   parseBeanDefinitions(root, this.delegate);
   postProcessXml(root);
}

註冊過程首先對profile進行處理,如果是環境變數定義的則進行處理,否則不進行處理。然後就是解析bean。這裡呼叫了preProcessXml(root)和postProcessXml(root)兩個方法,但是發現這兩個方法是空方法。這裡應用了模板方法模式。如果繼承自DefaultBeanDefinitionDocumentReader的子類需要在Bean解析前後做一些處理的話,只需要重寫這兩個方法即可。

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)) {
 // 如果元素是預設名稱空間,則按預設方式解析元素
               parseDefaultElement(ele, delegate);
            }
            else {
 // 如果這個元素是自定義名稱空間,則按自定義方式解析元素
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
// 如果根元素是自定義名稱空間,則按自定義方式解析元素
      delegate.parseCustomElement(root);
   }
}

而解析bean需要判斷元素是否是預設名稱空間,如果是則呼叫parseDefaultElement(ele, delegate)方法,不是則呼叫delegate.parseCustomElement(ele)方法。判斷是否是預設名稱空間,呼叫isDefaultNamespace方法,元素或者節點的名稱空間與Spring中固定的名稱空間http://www.springframework.org/schema/beans 進行對比,一致則認為是預設的,否則就認為是自定義的。

2.7 預設標籤的解析

預設標籤的解析邏輯一目瞭然,分別對4種不同標籤(import、alias、bean和beans)做了不同的處理。

2.7.1 bean標籤的解析及註冊

對bean標籤的解析是透過processBeanDefinition(ele, delegate)方法進行的。大致邏輯總結如下:

  1. 首先委託BeanDefinitionDelegate類的parseBeanDefinitionElement方法進行元素解析,返回BeanDefinitionHolder型別的例項bdHolder,經過這個方法後,bdHolder例項已經包含我們配置檔案中配置的各種屬性了,例如class、name、id、alias等屬性。
  2. 如果bdHolder不為空,若存在預設標籤的子節點下再有自定義屬性,還需要再次對自定義標籤進行解析。
  3. 解析後,對bdHolder進行註冊。註冊操作委託給了BeanDefinitionReaderUtils的registerBeanDefinition方法。
  4. 發出響應事件,通知相關監聽器,這個bean已經載入完了。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   // 解析元素資訊,用bdHolder封裝
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
// 如果標籤下有自定義屬性,則對自定義屬性進行解析
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      // 對bdHolder進行註冊
      BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      // 傳送註冊事件給監聽器.
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}

1)元素解析及資訊提取

首先我們從元素解析及資訊提取開始:delegate.parseBeanDefinitionElement(ele)。進入BeanDefinitionParserDelegate類parseBeanDefinitionElement方法,主要完成如下內容:

  1. 提取元素的id和name屬性
  2. 解析其他所有屬性,封裝到GenericBeanDefinition型別的例項中,對應this.parseBeanDefinitionElement(ele, beanName, containingBean)方法
  3. 如果bean沒有指定beanName,則使用預設規則為此Bean生成beanName
  4. 將獲取到的資訊封裝到BeanDefinitionHolder例項中

beanName取值策略是,首先取id,如果沒有指定id則取name[0](因為name可以指定多個),如果name也沒有指定,則採取自動生成方式生成。

最終bean元素的所有屬性和子元素資訊都儲存到GenericBeanDefinition中了。至此就完成了XML文件到GenericBeanDefinition的轉換。

2)預設標籤中自定義標籤元素的解析

如果這個bean使用的是預設的標籤配置,但是其中的子元素卻使用了自定義配置,這時這部分內容就起作用了。入口是delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)方法。它分別對元素的所有屬性和子元素進行了decorateIfRequired方法的呼叫。decorateIfRequired方法會判斷,如果是自定義節點,則找出自定義型別所對應的NamespaceHandler並進行進一步解析。

public BeanDefinitionHolder decorateIfRequired(
      Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {
   String namespaceUri = getNamespaceURI(node);
   if (!isDefaultNamespace(namespaceUri)) {
// 如果元素是自定義元素,則根據名稱空間找到對應的名稱空間處理器
      NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
      if (handler != null) {
 // 自定義名稱空間處理器處理bdHolder
         return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
      }
   }
   return originalDef;
}

3)BeanDefinition的註冊

BeanDefinition註冊分為透過beanName註冊BeanDefinition和註冊別名兩部分。進入BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry())方法內部,如下

public static void registerBeanDefinition(
      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
      throws BeanDefinitionStoreException {
   String beanName = definitionHolder.getBeanName();
// 透過beanName註冊BeanDefinition
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
   String[] aliases = definitionHolder.getAliases();
   if (aliases != null) {
      for (String alias : aliases) {
 // 註冊別名
         registry.registerAlias(beanName, alias);
      }
   }
}

透過beanName註冊BeanDefinition主要進行了4個步驟:

  1. 對AbstractBeanDefinition的校驗,主要是對於AbstractBeanDefinition的methodOverrides屬性的
  2. 對於beanName已經註冊的情況的處理,如果已經設定了不允許bean的覆蓋,會丟擲異常,否則進行覆蓋
  3. 加入Map快取,beanName為key,BeanDefinition為value
  4. 清除之前留下的對應beanName的快取
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
   if (beanDefinition instanceof AbstractBeanDefinition) {
 // 對AbstractBeanDefinition的校驗
      ((AbstractBeanDefinition) beanDefinition).validate();
   }
   BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName);
   if (oldBeanDefinition != null) {
      if (!isAllowBeanDefinitionOverriding()) {
 // 如果beanName已經註冊,並且設定了不允許bean覆蓋,會丟擲異常
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
               "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
               "': There is already [" + oldBeanDefinition + "] bound.");
      }
 // 將beanDefinition存到Map,beanName為key
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
   else {
      if (hasBeanCreationStarted()) {
         synchronized (this.beanDefinitionMap) {
 // 將beanDefinition存到Map,beanName為key
            this.beanDefinitionMap.put(beanName, beanDefinition);
         }
      }
      else {
         // 將beanDefinition存到Map,beanName為key
         this.beanDefinitionMap.put(beanName, beanDefinition);
      }
   }
   if (oldBeanDefinition != null || containsSingleton(beanName)) {
     // 清除之前留下的對應beanName的快取 
resetBeanDefinition(beanName);
   }
}

註冊別名的原理相對簡單,分為4個步驟:

  1. alias和beanName相同情況處理,此時會刪除原有的alias
  2. alias覆蓋處理,若aliasName已經使用了並已經指向了另一個beanName,且設定了別名不能覆蓋,則會丟擲異常
  3. alias迴圈檢查,如果出現了別名迴圈的情況,則丟擲異常
  4. 註冊alias
public void registerAlias(String name, String alias) {
    if (alias.equals(name)) {
        this.aliasMap.remove(alias);
    } else {
        String registeredName = (String)this.aliasMap.get(alias);
        if (registeredName != null) {
            if (registeredName.equals(name)) {
                return;
            }
 // 若aliasName已經使用了並已經指向了另一個beanName,且設定了別名不能覆蓋,則會丟擲異常
            if (!this.allowAliasOverriding()) {
                throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'.");
            }
        }
// alias迴圈檢查
        this.checkForAliasCircle(name, alias);
// alias註冊
        this.aliasMap.put(alias, name);
    }

}

4)通知監聽器解析及註冊完成

透過程式碼this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))完成此功能。這裡的實現只是為了擴充套件,當需要對註冊BeanDefinition事件進行監聽時可以透過註冊監聽器的方式並將處理邏輯寫入監聽器中,目前Spring沒有對此事件進行任何處理。

2.7.2 alias標籤的解析

對alias標籤的解析是透過processAliasRegistration(ele)方法處理的。

protected void processAliasRegistration(Element ele) {
   String name = ele.getAttribute(NAME_ATTRIBUTE);
   String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
   boolean valid = true;
   if (!StringUtils.hasText(name)) {
      valid = false;
   }
   if (!StringUtils.hasText(alias)) {
      valid = false;
   }
   if (valid) {
 // alias標籤解析
      getReaderContext().getRegistry().registerAlias(name, alias);
 // 通知監聽器
      getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
   }
}

進入方法內部可以看到,this.getReaderContext().getRegistry().registerAlias(name, alias)方法實現了alias標籤的解析,而這個方法實際就是前面註冊別名的那個方法。this.getReaderContext().fireAliasRegistered(name, alias, this.extractSource(ele))這個方法用於在別名註冊後通知監聽器做相應的處理,這裡的實現只是為了擴充套件,目前Spring沒有對此事件進行任何處理。

2.7.3 import標籤的解析

對import標籤的解析,Spring大致分為以下步驟:

  1. 獲取import標籤的resource屬性配置的路徑
  2. 解析路徑中的系統屬性,格式如“${user.dir}”,對應方法this.getReaderContext().getEnvironment().resolveRequiredPlaceholders(location)
  3. 判斷resource屬性配置的路徑是絕對路徑還是相對路徑
  4. 如果是絕對路徑,則呼叫bean的解析過程進行解析
  5. 如果是相對路徑則計算出絕對路徑後進行解析
  6. 通知監聽器,解析完成

不管是絕對路徑下import標籤的解析還是相對路徑下import標籤的解析,透過跟蹤程式碼發現,最後都會調到XmlBeanDefinitionReader類的loadBeanDefinitions方法,而這個方法在載入bean部分已經瞭解了。

2.7.4 嵌入式beans標籤的解析

該標籤解析呼叫的是DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions方法,而這個方法已經在解析及註冊BeanDefinitions部分了解了。嵌入式beans標籤和非嵌入式beans標籤的解析過程其實是一樣的。

2.8 自定義標籤的解析

自定義標籤非常有用,我們熟知的標籤就是採用自定義標籤的原理實現的。下面來探究一下自定義標籤的解析原理。
前面提到過解析自定義標籤的入口,檢視以下具體實現:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = this.getNamespaceURI(ele);
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        return null;
    } else {
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }
}

這裡傳入的containingBd為null。可以看到自定義標籤解析的思路特別簡單。無非是根據標籤元素獲取對應的名稱空間,根據名稱空間解析對應的處理器,然後根據使用者自定義的處理器進行解析。

2.8.1 解析自定義標籤處理器

透過元素可以獲取它的名稱空間,有了名稱空間就可以進行NamespaceHandler提取了。在readerContext初始化的時候其屬性namespaceHandlerResolver被初始化為DefaultNamespaceHandlerResolver的例項。所以呼叫resolve方法實際呼叫的是DefaultNamespaceHandlerResolver的方法。

public NamespaceHandler resolve(String namespaceUri) {
// 獲取名稱空間到處理器的對映關係
   Map<String, Object> handlerMappings = getHandlerMappings();
   Object handlerOrClassName = handlerMappings.get(namespaceUri);
   if (handlerOrClassName == null) {
      return null;
   }
   else if (handlerOrClassName instanceof NamespaceHandler) {
      return (NamespaceHandler) handlerOrClassName;
   }
   else {
      String className = (String) handlerOrClassName;
      Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
      if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
         throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
               "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
      }
 // 例項化名稱空間處理器
      NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
      namespaceHandler.init();
      handlerMappings.put(namespaceUri, namespaceHandler);
      return namespaceHandler;
   }
}

private Map<String, Object> getHandlerMappings() {
   if (this.handlerMappings == null) {
      synchronized (this) {
         if (this.handlerMappings == null) {
 // 載入配置檔案META-INF/spring.handlers
         Properties mappings =
               PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
         Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
         CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
         this.handlerMappings = handlerMappings;
         }
      }
   }
   return this.handlerMappings;
}

可以看到自定義標籤處理器的解析流程,首先透過this.getHandlerMappings()方法解析配置檔案獲取名稱空間到處理器的對映關係,Map儲存。然後例項化該名稱空間處理器,呼叫init()初始化方法。而定義的this.handlerMappingsLocation變數在呼叫構造方法的時候程式碼寫死了,是META-INF/spring.handlers。這個配置檔案是使用者自己去編寫的,定義名稱空間到處理器類的對映。名稱空間處理器類的實現也是需要使用者去實現,使用者可以繼承NamespaceHandlerSupport抽象類實現一下init()抽象方法。

2.8.2 標籤解析

我們已經得到了由哪個標籤處理器進行處理,接下來標籤解析由handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))去實現。

public BeanDefinition parse(Element element, ParserContext parserContext) {
// 獲取元素解析器,進行解析
   return findParserForElement(element, parserContext).parse(element, parserContext);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
   // 根據節點名稱獲取parser
String localName = parserContext.getDelegate().getLocalName(element);
   BeanDefinitionParser parser = this.parsers.get(localName);
   return parser;
}

在父類NamespaceHandlerSupport中可以看到,解析功能首先找到解析器,然後進行解析。查詢解析器首先獲取節點名稱,然後透過Map parsers獲取對應節點的解析器。而這個Map的賦值一般在使用者實現的名稱空間處理器init()方法中呼叫。

而自定義標籤的解析任務由parse方法完成。可以看到,首先透過parseInternal方法將標籤元素轉換成了BeanDefinition,然後解析id和name屬性並用BeanDefinitionHolder封裝元素資訊,接著進行BeanDefinition的註冊,最後通知監聽器。

public final BeanDefinition parse(Element element, ParserContext parserContext) {
 // 解析元素
    AbstractBeanDefinition definition = this.parseInternal(element, parserContext);
    if (definition != null && !parserContext.isNested()) {
 // 解析id
         String id = this.resolveId(element, definition, parserContext);
         String[] aliases = null;
         if (this.shouldParseNameAsAliases()) {
  // 解析name
             String name = element.getAttribute("name");
             if (StringUtils.hasLength(name)) {
                 aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
             }
         }
 // holder封裝元素資訊
         BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
         // 註冊
 this.registerBeanDefinition(holder, parserContext.getRegistry());
         if (this.shouldFireEvents()) {
             BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
             this.postProcessComponentDefinition(componentDefinition);
  // 通知監聽器
             parserContext.registerComponent(componentDefinition);
         }
    }
    return definition;
}

對於後面三步前面都介紹過了,只需看下parseInternal方法邏輯。

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
    // 解析parentName
 String parentName = this.getParentName(element);
    if (parentName != null) {
        builder.getRawBeanDefinition().setParentName(parentName);
    }
 // 解析beanClass
    Class<?> beanClass = this.getBeanClass(element);
    if (beanClass != null) {
        builder.getRawBeanDefinition().setBeanClass(beanClass);
    } else {
        String beanClassName = this.getBeanClassName(element);
        if (beanClassName != null) {
            builder.getRawBeanDefinition().setBeanClassName(beanClassName);
        }
    }
 // 解析source
    builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
    if (parserContext.isNested()) {
        builder.setScope(parserContext.getContainingBeanDefinition().getScope());
    }
 // 解析lazyInit
    if (parserContext.isDefaultLazyInit()) {
        builder.setLazyInit(true);
    }

    this.doParse(element, parserContext, builder);
    return builder.getBeanDefinition();
}

protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
    this.doParse(element, builder);
}

protected void doParse(Element element, BeanDefinitionBuilder builder) {
}

可以看到,parseInternal方法實際就是先解析parentName、beanClass、source、scope和lazyInit,以BeanDefinition封裝。然後呼叫doParse方法,這個方法也是使用者自定義解析器需要實現的方法。最後返回這個BeanDefinition。

3 總結

至此,我們一步一步地分析了Spring容器建立基本原理的所有內容。沒想到短短一句new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”)),Spring做了這麼多事:配置檔案的封裝、Document獲取和解析及註冊BeanDefinition等。解析及註冊BeanDefinition對預設標籤和自定義標籤進行不同的處理。對於預設標籤,又分別對bean標籤、alias標籤、import標籤和beans標籤進行不同的處理。其中bean標籤解析邏輯最為複雜也最為基礎,而剩下的那幾個標籤又複用了bean標籤處理的部分邏輯。

我們從中不僅可以學到Spring容器建立的基本原理,還可以學到許多編碼規範及技巧,瞭解到好的程式碼是什麼樣子的。比如其中應用到的單一職責原則、模板方法模式等等。而且還可以發現,它的程式碼邏輯很清晰,往往透過它的方法名稱就知道這個方法的功能,並且每個方法也不會特別長,增加了程式碼的可讀性和可維護性。並且程式碼封裝性很好,很多複雜的功能程式碼都可以複用。

在我們之後的開發工作中需要不斷學習好的編碼技巧及規範,應用到日常開發工作當中,最終形成我們自己的編碼技巧及風格。

4 參考資料

《Spring技術內幕:深入解析Spring架構與設計原理》
《Spring原始碼深度解析》


作者:曹銘(大件技術工坊)


相關文章