1. 程式人生 > >從零寫Spring註解版框架系列 IoC篇 (1) 框架設計

從零寫Spring註解版框架系列 IoC篇 (1) 框架設計

本文的註解版IoC框架跟其他手寫IoC框架的不同之處在與:在實現了 @Component 和 @Autowired 的同時還實現了@Qualifier,並解決單例模式下迴圈依賴的問題,以上3個註解的使用效果參照 Spring 。

專案 Github 地址為:https://github.com/linshenkx/winter-core
相關文章地址:
從零寫Spring註解版框架系列 IoC篇 (2)實現 @Component、@Autowired、@Qualifier註解

文章目錄

一 設計思想

1 IoC的定義

IoC 全稱為 Inversion of Control,翻譯為 “控制反轉”,它還有一個別名為 DI(Dependency Injection),即依賴注入。

所謂 IoC ,就是由 Spring IoC 容器來負責物件的生命週期和物件之間的關係

2 Spring中的 IoC

模組結構

傳統xml模式下的Spring實現IoC由幾個模組組成:

  1. Resource和ResourceLoader
    Spring 將資源的定義和資源的載入區分開:
    • Resource:統一資源,為 Spring 框架所有資源的抽象和訪問介面
    • ResourceLoader:統一資源定位,主要應用於根據給定的資原始檔地址,返回對應的 Resource
  2. BeanDefinition
    用來描述 Spring 中的 Bean 物件
  3. BeanDefinitionReader
    資源解析器,用於讀取 Spring 的配置檔案的內容,並將其轉換成 Ioc 容器內部的資料結構 :BeanDefinition
  4. BeanFactory
    一個非常純粹的 bean 容器,它是 IoC 必備的資料結構,其中 BeanDefinition 是它的基本結構。BeanFactory 內部維護著一個BeanDefinition map ,並可根據 BeanDefinition 的描述進行 bean 的建立和管理。
  5. ApplicationContext
    Spring 容器,也叫應用上下文,與我們應用息息相關。它繼承 BeanFactory ,是 BeanFactory 的擴充套件升級版

Spring IoC 的每一個模組都自成體系,設計精妙但也確實複雜繁重,初學者難免覺得巍然不可近。
單從原理思路出發的話則簡單很多,Spring IoC 無非就是替你管理了類的例項,在你需要的時候給你注入進去。

工作原理

IoC 初始化

IoC初始化階段負責從使用者指定路徑獲取bean的描述和配置資訊,將bean的描述資訊收歸 BeanFactory 的HashMap管理。
這個階段的操作目標是 bean的描述資訊(BeanDefinition)而不是 Bean本身
程式碼:

ClassPathResource resource = new ClassPathResource("bean.xml"); 
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); 
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); 
reader.loadBeanDefinitions(resource); 
  1. 資源定位
    使用者給定一個資源路徑(如xml的路徑,用file或者url等),ResourceLoader 根據策略從該路徑獲取到 Resource
  2. 裝載
    新建 IoC 容器 BeanFactory,根據 BeanFactory 建立一個 BeanDefinitionReader 物件對 Resource 進行解析,獲取到 BeanDefinition
  3. 註冊
    將得到的 BeanDefinition 注入到一個 HashMap 容器中,IoC 容器就是通過這個 HashMap 來維護這些 BeanDefinition 的
載入Bean

預設使用懶載入策略,即等到使用者需要這個 Bean 的時候再根據 BeanDefinition 完成 Bean 的載入
這裡的功能擴充套件比較多,但最主要解決的是 Bean的屬性填充(@Value)和依賴處理(@Autowired)
另外Bean的載入主要有單例(Singleton)和原型(prototype)兩種模式
需要注意的有 迴圈依賴 問題,對於單例模式出現的迴圈依賴Spring會進行分析檢測,對於原型模式則不管,由使用者負責。

二 設計思路

  1. 核心:@Component和@Autowired
    上面的分析是基於xml的,如果是使用註解的話還會簡單一些,免去了xml 讀取、解析的操作。
    核心功能通過 @Component 和 @Autowired 兩個註解即可完成。
    其中,@Component 負責將 Bean 交由 IoC 容器處理,@Autowired 負責將 IoC 容器管理的 Bean 注入的對應屬性中

  2. @Qualifier
    另外,@Autowired 預設是按照型別匹配的,Java官方還有一個@Resource註解是按照名稱匹配的,這裡我們還是使用主流的@Autowired註解,同時,為了證明不是偷懶我還會實現 @Qualifier 註解來指定名稱。

  3. 單例 VS 原型
    現在先完成單例模式,原型模式用得不多,不也推薦使用,對於有狀態的Bean推薦使用 ThreadLocal 來對狀態進行遮蔽。

  4. IoC 容器
    首先我們的功能核心放在一個 ApplicationContext 下,其實就只是一個 BeanFactory 而已。
    Bean的描述資訊BeanDefinition放在一個Map(beanDefinationFactory)裡面,因為我們用的是註解,BeanDefinition直接用對應類的 Class 物件就好。
    Bean例項化後的單例物件也放在一個Map裡面(singletonbeanFactory)。

  5. 確定載入的Bean
    由於我們可以在按照型別尋找的前提下根據使用者指定名進行匹配,所以不管是使用@Component進行Bean發現還是使用@Autowired進行Bean注入,要確定一個 Bean 的例項都需要兩個資訊:type(型別)和 beanId(名字)

    這裡比較麻煩的問題是父類可以由子類注入,介面可以由實現類注入。而且型別和名字並不是獨立匹配的,是在型別匹配的前提下進行名字識別,以獲取唯一bean

    為了達到匹配效果,我使用了這種結構來描述bean資訊(beanDefinationFactory) :Map<String,Map<String,Class>>
    ,即全稱類名(type)+自定義類名(beanId)==> 真類資訊

    為了達到每個bean只例項化一次,我用全稱類名來確定唯一bean例項(singletonbeanFactory):Map<String,Object>

  6. 載入流程
    同一個bean可能有多個型別(比方說其父類、介面等),所以在beanDefinationFactory中多個描述資訊可能對應的是一個Class
    首先根據type和beanId獲取Class物件
    再根據Class物件從singletonbeanFactory中獲取單例

三 功能目標

根據上文的設計思路,我們這個簡單的 IoC 註解版框架的 最基本功能是實現以下幾個註解:

1 註解設計

  1. @Component
    將註解的 Bean 納入 IoC框架管理,同時可以通過@Component的value指定該Bean的名稱(beanId),不指定則為類名首字母小寫,type為所有超類、介面和自身
  2. @Autowired
    將 IoC 框架管理的 Bean 注入註解標記的屬性
  3. @Qualifier
    搭配 @Autowired 實現在型別匹配的前提下按名稱匹配。

這麼說是不是很簡單?下面再來細化以下規則

2 規則細化

  1. IoC框架對於一個單例類只例項化一次
  2. 只使用@Autowired註解的情況下預設按型別匹配
    1. 如果 IoC 框架中僅有一個該型別的 Bean,則例項化該Bean
    2. 如果 IoC 框架中有多個該型別的 Bean,則產生歧義。此時根據 @Autowired 標記的屬性名(注意不是型別名)來識別唯一的Bean
      1. 如果在該型別的多個Bean找得到名稱匹配的Bean,則例項化該bean
      2. 如果找不到,則丟擲異常,提醒 存在多個同類不同名Bean,無法識別
  3. 如果在使用@Autowired的同時使用@Qualifier註解,則根據@Qualifier的value作為Bean的名稱在該型別下尋找匹配的bean
    1. 如果找得到,則例項化該Bean
    2. 如果沒找到,則丟擲異常,提醒該名稱的Bean不存在。注意,這時即使該型別下只有一個Bean,也會丟擲異常。

3 應用舉例

有類圖如下,其中HelloController、HelloServiceImpl、HelloServiceImpl1、HelloServiceImpl2都使用了@Component註解。

類圖
各類如下,以下注入可正常完成。

@Component
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "Hello!"+name;
    }
}

@Component("myHelloService1")
public class HelloServiceImpl1 implements HelloService {
    @Override
    public String sayHello(String name) {
        return "Hello1!"+name;
    }
}

@Component("myHelloService2")
public class HelloServiceImpl2 implements HelloService {
    @Override
    public String sayHello(String name) {
        return "Hello2!"+name;
    }
}

@Component
public class HelloController {
    //根據屬性名匹配,匹配到 HelloServiceImpl
    @Autowired
    private HelloService helloService;

    //根據屬性名匹配,匹配到 HelloServiceImpl2
    @Autowired
    private HelloService myHelloService2;

    //根據指定名匹配,匹配到 HelloServiceImpl1
    @Autowired
    @Qualifier("myHelloService1")
    private HelloService helloService1;

    //根據指定名匹配,匹配到 HelloServiceImpl2
    @Autowired
    @Qualifier("myHelloService2")
    private HelloService helloService2;

    //根據型別匹配,匹配到 HelloServiceImpl2
    @Autowired
    private HelloServiceImpl2 helloService22;

    public String helloDefault(String name){
        return helloService.sayHello(name);
    }
    public String hello1(String name){
        return helloService1.sayHello(name);
    }
    public String hello2(String name){
        return myHelloService2.sayHello(name);
    }

}