我經過實現一個簡易的 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
首先,Spring 須要實例化類,將其轉換爲對象。在 Spring 中,咱們管(業務)類叫 Bean,因此實例化類也能夠稱爲實例化 Bean。
早期 Spring 須要藉助 xml 配置文件來實現實例化 Bean,能夠分爲三步(配合源碼 v1 閱讀):
關於類加載和反射,前者能夠看看《深刻理解 Java 虛擬機》第 7 章,後者能夠看看《廖雪峯 Java 教程》反射 部分。本文只學習 Spring,這兩個知識點不作深刻討論。
名詞解釋:
本節源碼對應:v1
實現實例化 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 閱讀):
<property>
,表明對應 Setter() 方法。<property>
,存放到 BeanDefinition 的 propertyNames 中。基於構造函數實現依賴注入的方式跟 Setter() 方法差很少,感興趣能夠 Google 搜索查看。
由於 Spring 實現了依賴注入,因此咱們程序員沒有了建立對象的控制權,因此也被稱爲控制反轉(Inversion of Control,簡稱 IoC)。由於 Spring 使用 Map 管理 BeanDefinition,咱們也能夠將 Spring 稱爲 IoC 容器。
本節源碼對應:v2
前面兩步實現了獲取 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
前面使用 xml 配置文件的方式,實現了實例化 Bean 和依賴注入。這種方式比較麻煩,還容易出錯。Spring 從 2.5ref 開始可以使用註解替代 xml 配置文件。好比:
<bean>
<property>
@Component 用於生成 BeanDefinition,原理(配合源碼 v4 閱讀):
@Autowired 用於依賴注入,原理(配合源碼 v4 閱讀):
至此,咱們仍是在須要經過配置文件來實現組件掃描。有沒有徹底不使用配置文件的方式?有!
咱們可使用 @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 查閱。
名詞解釋:
本節源碼對應:v4
說到了 @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 包的組件注入容器?
<context:component-scan base-package="引入 jar 包的 base-package" /> ...
嗯,本節沒有源碼
以上實現了一個簡易的 Spring IoC 容器,順便說了一下 Spring Boot 原理。Spring 還有不少重要功能,如:管理 Bean 生命週期、AOP 的實現,等等。後續有機會再作一次分享。
來個註解小結:
有的童鞋可能還會有這樣的疑問:
jdk jar 包、工具 jar 包的類是否須要注入容器?
全文完。
本文由博客一文多發平臺 OpenWrite 發佈!