1:前言
回顧探索Spring框架
1.spring ioc
IoC其實有兩種方式,一種就是DI,而另一種是DL,即Dependency Lookup(依賴查找),前者是當前軟件實體被動接受其依賴的其他組件被IoC容器注入,而后者則是當前軟件實體主動去某個服務注冊地查找其依賴的那些服務,概念之間的關系如圖2-1所示可能更貼切些。
那我們還是回過頭來繼續說Spring IoC容器的依賴注入流程吧!Spring IoC容器的依賴注入工作可以分為兩個階段:
階段一:收集和注冊
第一個階段可以認為是構建和收集bean定義的階段,在這個階段中,我們可以通過XML或者Java代碼的方式定義一些bean,然后通過手動組裝或者讓容器基於某些機制自動掃描的形式,將這些bean定義收集到IoC容器中。
階段二:分析和組裝
當第一階段工作完成后,我們可以先暫且認為IoC容器中充斥着一個個獨立的bean,它們之間沒有任何關系。但實際上,它們之間是有依賴關系的,所以,IoC容器在第二階段要干的事情就是分析這些已經在IoC容器之中的bean,然后根據它們之間的依賴關系先后組裝它們。如果IoC容器發現某個bean依賴另一個bean,它就會將這另一個bean注入給依賴它的那個bean,直到所有bean的依賴都注入完成,所有bean都“整裝待發”,整個IoC容器的工作即算完成。
正文
我們開發任何一個Spring Boot項目,都會用到如下的啟動類
package com.dongk.chapter001; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Chapter001Application { public static void main(String[] args) { SpringApplication.run(Chapter001Application.class, args); } }
我們一起看看這個唯一的注解@SpringBootApplication
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication {...}
雖然定義使用了多個Annotation進行了原信息標注,但實際上重要的只有三個Annotation:
- @Configuration(@SpringBootConfiguration點開查看發現里面還是應用了@Configuration)
- @EnableAutoConfiguration
- @ComponentScan
@Configuration
這里的@Configuration對我們來說不陌生,它就是JavaConfig形式的Spring Ioc容器的配置類使用的那個@Configuration,SpringBoot社區推薦使用基於JavaConfig的配置形式,所以,這里的啟動類標注了@Configuration之后,本身其實也是一個IoC容器的配置類。
舉幾個簡單例子回顧下,XML跟config配置方式的區別:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"> </beans>
@Configuration
public class MockConfiguration{ //bean 定義 }
<bean id="mockService" class="..MockServiceImpl">
...
</bean>
@Configuration
public class MockConfiguration{ @Bean public MockService mockService(){ return new MockServiceImpl(); } }
<bean id="mockService" class="..MockServiceImpl"> <property name="dependencyService" ref="dependencyService"/> </bean> <bean id="dependencyService" class="DependencyServiceImpl"/>
@Configuration
public class MockConfiguration{ @Bean public MockService mockService(){ return new MockServiceImpl(dependcyService()); } @Bean public DependcyService dependcyService(){ return new DependcyServiceImpl(); } }
@ComponentScan
@ComponentScan這個注解在Spring中很重要,它對應XML配置中的元素,@ComponentScan的功能其實就是自動掃描並加載符合條件的組件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義加載到IoC容器中。
我們可以通過basePackages等屬性來細粒度的定制@ComponentScan自動掃描的范圍,如果不指定,則默認Spring框架實現會從聲明@ComponentScan所在類的package進行掃描。
@EnableAutoConfiguration
Spring框架提供的各種名字為@Enable開頭的Annotation定義?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其實一脈相承,簡單概括一下就是,借助@Import的支持,收集和注冊特定場景相關的bean定義。
- @EnableScheduling是通過@Import將Spring調度框架相關的bean定義都加載到IoC容器。
- @EnableMBeanExport是通過@Import將JMX相關的bean定義加載到IoC容器。
而@EnableAutoConfiguration也是借助@Import的幫助,將所有符合自動配置條件的bean定義加載到IoC容器,僅此而已!
@EnableAutoConfiguration作為一個復合Annotation,其自身定義關鍵信息如下:
其中,最關鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應用將所有符合條件的@Configuration配置都加載到當前SpringBoot創建並使用的IoC容器。就像一只“八爪魚”一樣
借助於Spring框架原有的一個工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自動配置功效才得以大功告成!
自動配置幕后英雄:SpringFactoriesLoader詳解
SpringFactoriesLoader屬於Spring框架私有的一種擴展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置。
public abstract class SpringFactoriesLoader { //... public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) { ... } public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { .... } }
配合@EnableAutoConfiguration使用的話,它更多是提供一種配置查找的功能支持,即根據@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對應的一組@Configuration類
流程簡圖如下
流程詳解
SpringApplication的run方法的實現是我們本次旅程的主要線路,該方法的主要流程大體可以歸納如下:
1) 如果我們使用的是SpringApplication的靜態run方法,那么,這個方法里面首先要創建一個SpringApplication對象實例,然后調用這個創建好的SpringApplication的實例方法。在SpringApplication實例初始化的時候,它會提前做幾件事情:
- 根據classpath里面是否存在某個特征類(org.springframework.web.context.ConfigurableWebApplicationContext)來決定是否應該創建一個為Web應用使用的ApplicationContext類型。
- 使用SpringFactoriesLoader在應用的classpath中查找並加載所有可用的ApplicationContextInitializer。
- 使用SpringFactoriesLoader在應用的classpath中查找並加載所有可用的ApplicationListener。
- 推斷並設置main方法的定義類。
2) SpringApplication實例初始化完成並且完成設置后,就開始執行run方法的邏輯了,方法執行伊始,首先遍歷執行所有通過SpringFactoriesLoader可以查找到並加載的SpringApplicationRunListener。調用它們的started()方法,告訴這些SpringApplicationRunListener,“嘿,SpringBoot應用要開始執行咯!”。
3) 創建並配置當前Spring Boot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。
4) 遍歷調用所有SpringApplicationRunListener的environmentPrepared()的方法,告訴他們:“當前SpringBoot應用使用的Environment准備好了咯!”。
5) 如果SpringApplication的showBanner屬性被設置為true,則打印banner。
6) 根據用戶是否明確設置了applicationContextClass類型以及初始化階段的推斷結果,決定該為當前SpringBoot應用創建什么類型的ApplicationContext並創建完成,然后根據條件決定是否添加ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當然,最重要的,將之前准備好的Environment設置給創建好的ApplicationContext使用。
7) ApplicationContext創建好之后,SpringApplication會再次借助Spring-FactoriesLoader,查找並加載classpath中所有可用的ApplicationContext-Initializer,然后遍歷調用這些ApplicationContextInitializer的initialize(applicationContext)方法來對已經創建好的ApplicationContext進行進一步的處理。
8) 遍歷調用所有SpringApplicationRunListener的contextPrepared()方法。
9) 最核心的一步,將之前通過@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置加載到已經准備完畢的ApplicationContext。
10) 遍歷調用所有SpringApplicationRunListener的contextLoaded()方法。
11) 調用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。
12) 查找當前ApplicationContext中是否注冊有CommandLineRunner,如果有,則遍歷執行它們。
13) 正常情況下,遍歷執行SpringApplicationRunListener的finished()方法、(如果整個過程出現異常,則依然調用所有SpringApplicationRunListener的finished()方法,只不過這種情況下會將異常信息一並傳入處理)