造輪子:實現一個簡易的 Spring IoC 容器

做者:DeppWang原文地址git

source:https://fernandofranzini.wordpress.com/

我經過實現一個簡易的 Spring IoC 容器,算是入門了 Spring 框架。本文是對實現過程的一個總結提煉,須要配合源碼閱讀源碼地址程序員

結合本文和源碼,你應該能夠學到:Spring 的原理和 Spring Boot 的原理。github

Spring 框架是 Java 開發的,Java 是面向對象的語言,因此 Spring 框架自己有大量的抽象、繼承、多態。對於初學者來講,光是理清他們的邏輯就很麻煩,我摒棄了那些包裝,只實現了最本質的功能。代碼不是很嚴謹,但只爲了理解 Spring 思想卻夠了。spring

下面正文開始。框架

零、前言

在沒有 Spring 框架的遠古時代,咱們業務邏輯長這樣:ide

public class PetStoreService {
    AccountDao accountDao = new AccountDao();
}

public class AccountDao {
}

PetStoreService petStoreService = new PetStoreService();

處處都是 new 關鍵字,須要開發人員顯式的使用 new 關鍵字來建立業務類對象(實例)。這樣有不少弊端,如,建立的對象太多,耦合性太強,等等。wordpress

有個叫 Rod Johnson 老哥對此很不爽,就開發了一個叫 Spring 的框架,就是爲了幹掉 new 關鍵字(哈哈,我杜撰的,只是爲了說明 Spring 的做用)。函數

有了 Spring 框架,由框架來新建對象,管理對象,並處理對象之間的依賴,咱們程序員不再用 new 業務類對象了。咱們來看看 Spring 框架是如何實現的吧。spring-boot

注:如下 Spring 框架簡寫爲 Spring工具

本節源碼對應:v0

1、實現「實例化 Bean 」

首先,Spring 須要實例化類,將其轉換爲對象。在 Spring 中,咱們管(業務)類叫 Bean,因此實例化類也能夠稱爲實例化 Bean。

早期 Spring 須要藉助 xml 配置文件來實現實例化 Bean,能夠分爲三步(配合源碼 v1 閱讀):

  1. 從 xml 配置文件獲取 Bean 信息,如全限定名等,將其做爲 BeanDefinition(Bean 定義類)的屬性
  2. 使用一個 Map 存放全部 BeanDefinition,此時 Spring 本質上是一個 Map,存放 BeanDefinition
  3. 當獲取 Bean 實例時,經過類加載器,根據全限定名,獲得其類對象,經過類對象利用反射建立 Bean 實例

關於類加載和反射,前者能夠看看《深刻理解 Java 虛擬機》第 7 章,後者能夠看看《廖雪峯 Java 教程》反射 部分。本文只學習 Spring,這兩個知識點不作深刻討論。

名詞解釋:

  • 全限定名:指編譯後的 class 文件在 jar 包中的路徑

本節源碼對應:v1

2、實現「填充屬性(依賴注入)」

實現實例化 Bean 後,此時成員變量(引用)還爲 null:

此時須要經過一種方式實現,讓引用指向實例,咱們管這一步叫填充屬性。

當一個 Bean 的成員變量類型是另外一個 Bean 時,咱們能夠說一個 Bean 依賴於另外一個 Bean。因此填充屬性,也能夠稱爲依賴注入(Dependency Injection,簡稱 DI)。

拋開 Spring 不談,在正常狀況下,咱們有兩種方式實現依賴注入,一、使用 Setter() 方法,二、使用構造方法。使用 Setter() 方法以下:

public class PetStoreService {
    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
}

public class AccountDao {
}

PetStoreService petStore = new PetStoreService();
petStore.setAccountDao(new AccountDao()); // 將依賴 new AccountDao() 注入 petStore

其實早期 Spring 也是經過這兩種方式來實現依賴注入的。下面是 Spring 經過 xml 文件 + Setter() 來實現依賴注入的步驟(配合源碼 v2 閱讀):

  1. 給 PetStoreService 添加 Setter() 方法,並稍微修改一下 xml 配置文件,添加 <property>,表明對應 Setter() 方法。
  2. 從 xml 配置文件獲取 Bean 的屬性 <property>,存放到 BeanDefinition 的 propertyNames 中。
  3. 經過 propertyName 獲取屬性實例,利用反射,經過 Setter() 方法實現填充屬性(依賴注入)

基於構造函數實現依賴注入的方式跟 Setter() 方法差很少,感興趣能夠 Google 搜索查看。

由於 Spring 實現了依賴注入,因此咱們程序員沒有了建立對象的控制權,因此也被稱爲控制反轉(Inversion of Control,簡稱 IoC)。由於 Spring 使用 Map 管理 BeanDefinition,咱們也能夠將 Spring 稱爲 IoC 容器。

本節源碼對應:v2

3、使用「單例模式、工廠方法模式」

前面兩步實現了獲取 Bean 實例時建立 Bean 實例,但 Bean 實例常用,不能每次都新建立。其實在 Spring 中,一個業務類只對應一個 Bean 實例,這須要使用單例模式。

單例模式:一個類有且只有一個實例

Spring 使用類對象建立 Bean 實例,是如何實現單例模式的?

Spring 其實使用一個 Map 存放全部 Bean 實例。建立時,先看 Map 中是否有 Bean 實例,沒有就建立;獲取時,直接從 Map 中獲取。這種方式能保證一個類只有一個 Bean 實例。

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(64);

早期 Spring 使用 Bean 的策略是用到時再實例化所用 Bean,傑出表明是 XmlBeanFactory,後期爲了實現更多的功能,新增了 ApplicationContext,二者都繼承於 BeanFactory 接口。這使用了工廠方法模式。

工廠方法模式:定義一個用於建立對象的接口,讓子類決定實例化哪個類。Factory Method 使一個類的實例化延遲到其子類。

咱們將 BeanIocContainer 修改成 BeanFactory 接口,只提供 getBean() 方法。建立(IoC)容器由其子類本身實現。

ApplicationContext 和 BeanFactory 的區別:ApplicationContext 初始化時就實例化全部 Bean,BeanFactory 用到時再實例化所用 Bean。

本節源碼對應:v3

3、實現「註解」

前面使用 xml 配置文件的方式,實現了實例化 Bean 和依賴注入。這種方式比較麻煩,還容易出錯。Spring 從 2.5ref 開始可以使用註解替代 xml 配置文件。好比:

  1. 使用 @Component 註解代替 <bean>
  2. 使用 @Autowired 註解代替 <property>

@Component 用於生成 BeanDefinition,原理(配合源碼 v4 閱讀):

  • 根據 component-scan 指定路徑,找到路徑下全部包含 @Component 註解的 Class 文件,做爲 BeanDefinition
  • 如何判斷 Class 是否有 @Component:利用字節碼技術,獲取 Class 文件中的元數據(註解等),判斷元數據中是否有 @Componet

@Autowired 用於依賴注入,原理(配合源碼 v4 閱讀):

  • 經過反射,查看 Field 中是否有 @Autowired 類型的註解,有,則使用反射實現依賴注入

至此,咱們仍是在須要經過配置文件來實現組件掃描。有沒有徹底不使用配置文件的方式?有!

咱們可使用 @Configuration 替代配置文件,並使用 @ComponentScan 來替代配置文件的 <context:component-scan>

@Configuration // 將類標記爲 @Configuration,表明這個類是至關於一個配置文件
@ComponentScan // ComponentScan 掃描 PetStoreConfig.class 所在路徑及其所在路徑全部子路徑的文件
public class PetStoreConfig {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(PetStoreConfig.class);
        PetStoreService userService = context.getBean(PetStoreService.class);
        userService.getAccountDao();
    }
}

使用註解其實跟使用 xml 配置文件同樣,目的是將配置類做爲入口,實現掃描組件,將其加載進 IoC 容器中的功能。

AnnotationConfigApplicationContext 是專爲針對配置類的啓動類。其實現機制,能夠 Google 查閱。

名詞解釋:

  • Component:組件
  • Autowired:自動裝配

本節源碼對應:v4

4、Spring Boot 原理

說到了 @Configuration 和 @ComponentScan,就不得不提 Spring Boot。由於 Spring Boot 就使用了 @Configuration 和 @ComponentScan,你能夠點開 @SpringBootApplication 看到。

咱們發現,Spring Boot 啓動時,並無使用 AnnotationConfigApplicationContext 來指定啓動某某 Config 類。這是由於它使用了 @EnableAutoConfiguration 註解。

Spring Boot 利用了 @EnableAutoConfiguration 來自動加載標識爲 @Configuration 的配置類到容器中。Spring Boot 還能夠將須要自動加載的配置類放在 spring.factories 中,Spring Boot 將自動加載 spring.factories 中的配置類。spring.factories 需放置於META-INF 下。

如 Spring Boot 項目啓動時,autocofigure 包中將自動加載到容器的(部分)配置類以下:

以上也是 Spring Boot 的原理。

在 Spring Boot 中,咱們引入的 jar 包都有一個字段,starter,咱們叫 starter 包。

標識爲 starter(啓動器)是由於引入這些包時,咱們不用設置額外操做,它能被自動裝配,starter 包通常都包含本身的 spring.factories。如 spring-cloud-starter-eureka-server:

如 druid-spring-boot-starter:

有時候咱們還須要自定義 starter 包,好比在 Spring Cloud 中,當某個應用要調用另外一個應用的代碼時,要麼調用方使用 Feign(HTTP),要麼將被調用方自定義爲 starter 包,讓調用方依賴引用,再 @Autowired 使用。此時須要在被調用方設置配置類和 spring.factories:

@Configuration
@ComponentScan
public class ProviderAppConfiguration {
}

// spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.amy.cloud.amycloudact.ProviderAppConfiguration

固然,你也能夠把這兩個文件放在調用方(此時要指定掃描路徑),但通常放在被調用方。ps:若是你兩個應用的 base-package 路徑同樣,那麼能夠不用這一步。

說了 Spring Boot,那麼在 Spring MVC,如何將引入 jar 包的組件注入容器?

  • 跟掃描本項目包同樣,在 xml ,增長引入 jar 包的掃描路徑:
<context:component-scan base-package="引入 jar 包的 base-package" />
...

嗯,本節沒有源碼

5、結語

以上實現了一個簡易的 Spring IoC 容器,順便說了一下 Spring Boot 原理。Spring 還有不少重要功能,如:管理 Bean 生命週期、AOP 的實現,等等。後續有機會再作一次分享。

來個註解小結:

  • Spring 只實例化標識爲 @Component 的組件(即業務類對象)
  • @Component 做爲組件標識
  • @Autowired 用於判斷是否須要依賴注入
  • @ComponentScan 指定組件掃描路徑,不指定即爲當前路徑
  • @Configuration 表明配置類,做爲入口
  • @EnableAutoConfiguration 實現加載配置類

有的童鞋可能還會有這樣的疑問:

jdk jar 包、工具 jar 包的類是否須要注入容器?

  • 回答是不須要,由於容器只管理業務類,注入容器的類都有 @Component 註解。

全文完。

本文由博客一文多發平臺 OpenWrite 發佈!

相關文章
相關標籤/搜索