Spring系列之手寫註解與配置檔案的解析

寧願。發表於2018-12-27

目錄

引入

在前面我們已經完成了IOC,DI,AOP的實現,基本的功能都已經完成了,我們的手寫框架也能勉強使用起來。為了讓我們的框架能夠使用起來比較簡單,這一節我們來實現註解和xml的配置。

tips

本章的xml和註解的功能都是為實現bean的建立,其他如aop等功能可仿造實現。

為什麼要加註解和xml配置

如果有同學測試過我們寫好的框架,可能會感受到使用起來非常麻煩,在測試的時候我們需要顯示的來定義bean以及執行過程中需要的其他物件。

    public void test() throws Exception {
        DefaultBeanDefinition bd = new DefaultBeanDefinition();
        bd.setClazz(User.class);
        bd.setSingleton(true);
        bd.setBeanFactoryName("TestFactory");
        bd.setCreateBeanMethodName("createMethod");
        bd.setStaticCreateBeanMethodName("staticCreateMethod");

        factory.register(bd, "user");
        bd = new DefaultBeanDefinition();
        bd.setClazz(BeforeAdvice.class);
        factory.register(bd, "myBeforeAdvice");

        AopProxyCreator aapc = new AopProxyCreator();
        aapc.setBeanFactory(factory);
        factory.registerBeanPostProcessor(aapc);
        // 向AdvisorAutoProxyCreator註冊Advisor
        aapc.register(new RegexMatchAdvisor("myBeforeAdvice", "execution(* bean.User.*())", new RegexExpressionPointCutResolver()));
        User user = (User) factory.doGetBean("user");
        user.sayHello();
    }
複製程式碼

如上為測試一個AOP的功能,需要定義很多的物件來完成功能,這還只是一個物件的功能增強,在實際使用中肯定會有大量的例項。這樣在使用起來就變得及其麻煩了。參考Spring中,可以通過xml和annotation的方式來簡化類定義或者其他一些處理。

註解和xml的整個處理過程

在實際實現之前,我們先來巨集觀的看一些註解和xml是如何來解析的。

xml

基本上在spring中xml就是這樣工作的,同理,註解也差不多是這樣一個過程。

註解

上面的這一過程實際上就是我們需要實現的功能,現在我們就根據以上過程進行實現吧。

XML

定義XML標記

這一小節的內容並不重要,實際就我們的開發中作用並不大,稍微瞭解即可。沒興趣的可以直接跳到下一節。

定義xml標記的方式有dtd和xsd兩種,假設我想定義一個下面這樣的xml標記:

<?xml version="1.0"?>
<note>
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>
複製程式碼

那麼我們既可以用dtd實現,也可以用xsd實現:

dtd

<!ELEMENT note (to, from, heading, body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
複製程式碼

xsd

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.w3schools.com"
xmlns="http://www.w3schools.com"
elementFormDefault="qualified">

<xs:element name="note">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="to" type="xs:string"/>
      <xs:element name="from" type="xs:string"/>
      <xs:element name="heading" type="xs:string"/>
      <xs:element name="body" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:element>

</xs:schema>
複製程式碼

這節內容只是稍微提下,有興趣的可以搜一些資料學習。

載入xml檔案

要來載入檔案那麼首先我們需要知道這個檔案的位置,很明顯這個位置需要由使用者來指定,由使用者來告訴我們需要載入那些配置檔案,那麼使用者該如何來指定呢?

這裡我們需要定義新的類和介面來完成這件事,這個新定義的類用來完成xml檔案的載入,解析以及對bean例項的建立和註冊等。同時該類是給使用者使用的,在bean建立後還需要讓使用者能夠取到容器中的例項,即該類還需要具備beanfactory的部分功能。

ApplicationContext

如何載入配置檔案

說到載入,首先我們需要明白這些配置檔案可能會以什麼樣的形式展現,一般來說有著以下的型別:

  • FileSystem:從本地檔案載入
  • URL:從網路獲取
  • ClassPath:在專案中讀取配置

可能還有其他不同的形式,當然這不重要。我們需要明白的是很明顯不同形式的內容載入的方式肯定是不同的,比如通過檔案系統載入的是獲得一個檔案,而url形式的是從網路獲取資料。所以我們需要為每一種方式都提供個性的載入方式。

載入的方式不一樣,那麼解析呢?

解析xml,我們都是需要獲取到xml的流資訊,都是解析xml的InputStream,所以雖然載入方式各不相同,但是解析的方法是可以通用的。

根據上面的分析,我們可以知道,載入的過程實際上就是獲取一個InputStream的過程,很明顯每一種方式獲取配置檔案都需要最終返回一個InputStream,這裡我們定義一個獲取InputStream的介面。

InputStream

對於配置檔案的載入實際上不管通過哪一種方式,實際上都是對檔案進行操作,為了方便能夠對解析提供一致的介面,我們需要對不同方式載入進行抽象,使其能接受任意型別的載入方式物件。這裡我們定義Resource介面來表示xml資源,不同的實現類表示不同的載入方式。Resource介面具備一部分檔案的特性。

Resource

到了現在我們還有一個很重要的問題,就是我們如何來分辨當前需要載入的配置是屬於哪一種型別呢?只有解決了這一問題才能正確的解析。

這裡我們使用字首匹配的方式來分配不同型別的配置檔案載入器,比如:

  • FileSystem:使用file:字首
  • ClassPath:使用classpath:字首
  • URL:可以使用url:字首,或者直接使用instanceof來校驗物件

通過不同的字首來來返回不同的物件,如果看過前面幾節的同學應該馬上就能反應過來這裡肯定要用工廠模式了[笑]。

那麼載入配置檔案這一行為在什麼時候進行呢?

考慮到類ApplicationContext是使用者使用框架的入口,該類還包含獲取bean例項的操作,要保證在該類例項化完成後讓ApplicationContext具備獲取例項的能力,那麼載入配置檔案的功能就需要在ApplicationContext的構造方法中完成。

結合以上分析,對ApplicationContext類圖做出修改。

ResourceFactory

如何解析配置檔案

載入已經完成了,就是說我們現在已經取到不同的Resource,那麼現在如何對其進行解析呢?

對於xml型別的檔案解析我們這裡使用dom4j,這個東西沒必要深入研究,要用到的時候查下api就行了。對於xml的解析就是獲取節點,然後對獲取節點的內容。回顧我們之前在做IOC的時候,建立一個bean首先是需要獲取BeanDefinition。xml這裡也一樣,根據解析到的內容構造BeanDefinition,通過BeanDefinitionRegistery註冊。在對bean例項化時取用。所以很明顯我們需要定義一個新的介面用於解析Resource,將解析後的內容封裝為BeanDefinition並註冊。

Reader

好了,到這裡關於xml的載入和解析基本就完成了,而對於annotation的解析流程基本也是這樣的,使用者指定需要掃描的包,框架遍歷包所在目錄及子目錄,記錄下相應被註解修飾的類,反射生成class物件,獲取class物件資料生成BeanDefinition。類圖在上面也已經體現出來了。後面就需要我們去寫程式碼了。

相關的程式碼已經託管到github

相關文章