從零寫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由幾個模組組成:
- Resource和ResourceLoader
Spring 將資源的定義和資源的載入區分開:- Resource:統一資源,為 Spring 框架所有資源的抽象和訪問介面
- ResourceLoader:統一資源定位,主要應用於根據給定的資原始檔地址,返回對應的 Resource
- BeanDefinition
用來描述 Spring 中的 Bean 物件 - BeanDefinitionReader
資源解析器,用於讀取 Spring 的配置檔案的內容,並將其轉換成 Ioc 容器內部的資料結構 :BeanDefinition - BeanFactory
一個非常純粹的 bean 容器,它是 IoC 必備的資料結構,其中 BeanDefinition 是它的基本結構。BeanFactory 內部維護著一個BeanDefinition map ,並可根據 BeanDefinition 的描述進行 bean 的建立和管理。 - 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);
- 資源定位
使用者給定一個資源路徑(如xml的路徑,用file或者url等),ResourceLoader 根據策略從該路徑獲取到 Resource - 裝載
新建 IoC 容器 BeanFactory,根據 BeanFactory 建立一個 BeanDefinitionReader 物件對 Resource 進行解析,獲取到 BeanDefinition - 註冊
將得到的 BeanDefinition 注入到一個 HashMap 容器中,IoC 容器就是通過這個 HashMap 來維護這些 BeanDefinition 的
載入Bean
預設使用懶載入策略,即等到使用者需要這個 Bean 的時候再根據 BeanDefinition 完成 Bean 的載入
這裡的功能擴充套件比較多,但最主要解決的是 Bean的屬性填充(@Value)和依賴處理(@Autowired)
另外Bean的載入主要有單例(Singleton)和原型(prototype)兩種模式
需要注意的有 迴圈依賴 問題,對於單例模式出現的迴圈依賴Spring會進行分析檢測,對於原型模式則不管,由使用者負責。
二 設計思路
-
核心:@Component和@Autowired
上面的分析是基於xml的,如果是使用註解的話還會簡單一些,免去了xml 讀取、解析的操作。
核心功能通過 @Component 和 @Autowired 兩個註解即可完成。
其中,@Component 負責將 Bean 交由 IoC 容器處理,@Autowired 負責將 IoC 容器管理的 Bean 注入的對應屬性中 -
@Qualifier
另外,@Autowired 預設是按照型別匹配的,Java官方還有一個@Resource註解是按照名稱匹配的,這裡我們還是使用主流的@Autowired註解,同時,為了證明不是偷懶我還會實現 @Qualifier 註解來指定名稱。 -
單例 VS 原型
現在先完成單例模式,原型模式用得不多,不也推薦使用,對於有狀態的Bean推薦使用 ThreadLocal 來對狀態進行遮蔽。 -
IoC 容器
首先我們的功能核心放在一個 ApplicationContext 下,其實就只是一個 BeanFactory 而已。
Bean的描述資訊BeanDefinition放在一個Map(beanDefinationFactory)裡面,因為我們用的是註解,BeanDefinition直接用對應類的 Class 物件就好。
Bean例項化後的單例物件也放在一個Map裡面(singletonbeanFactory)。 -
確定載入的Bean
由於我們可以在按照型別尋找的前提下根據使用者指定名進行匹配,所以不管是使用@Component進行Bean發現還是使用@Autowired進行Bean注入,要確定一個 Bean 的例項都需要兩個資訊:type(型別)和 beanId(名字)這裡比較麻煩的問題是父類可以由子類注入,介面可以由實現類注入。而且型別和名字並不是獨立匹配的,是在型別匹配的前提下進行名字識別,以獲取唯一bean
為了達到匹配效果,我使用了這種結構來描述bean資訊(beanDefinationFactory) :Map<String,Map<String,Class>>
,即全稱類名(type)+自定義類名(beanId)==> 真類資訊為了達到每個bean只例項化一次,我用全稱類名來確定唯一bean例項(singletonbeanFactory):Map<String,Object>
-
載入流程
同一個bean可能有多個型別(比方說其父類、介面等),所以在beanDefinationFactory中多個描述資訊可能對應的是一個Class
首先根據type和beanId獲取Class物件
再根據Class物件從singletonbeanFactory中獲取單例
三 功能目標
根據上文的設計思路,我們這個簡單的 IoC 註解版框架的 最基本功能是實現以下幾個註解:
1 註解設計
- @Component
將註解的 Bean 納入 IoC框架管理,同時可以通過@Component的value指定該Bean的名稱(beanId),不指定則為類名首字母小寫,type為所有超類、介面和自身 - @Autowired
將 IoC 框架管理的 Bean 注入註解標記的屬性 - @Qualifier
搭配 @Autowired 實現在型別匹配的前提下按名稱匹配。
這麼說是不是很簡單?下面再來細化以下規則
2 規則細化
- IoC框架對於一個單例類只例項化一次
- 只使用@Autowired註解的情況下預設按型別匹配
- 如果 IoC 框架中僅有一個該型別的 Bean,則例項化該Bean
- 如果 IoC 框架中有多個該型別的 Bean,則產生歧義。此時根據 @Autowired 標記的屬性名(注意不是型別名)來識別唯一的Bean
- 如果在該型別的多個Bean找得到名稱匹配的Bean,則例項化該bean
- 如果找不到,則丟擲異常,提醒 存在多個同類不同名Bean,無法識別
- 如果在使用@Autowired的同時使用@Qualifier註解,則根據@Qualifier的value作為Bean的名稱在該型別下尋找匹配的bean
- 如果找得到,則例項化該Bean
- 如果沒找到,則丟擲異常,提醒該名稱的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);
}
}