1. IOC和DI
首先,我們應該明確,IOC是一種思想,并不是Spring特有的,而是軟件工程逐步發展的一種產物,是一種優秀的編程思想,之所以我們經常會把IOC理解成是Spring特有的東西,是因為Spring框架可以幫助我們很好的去實現IOC。IOC代表的是控制反轉,控制的是什么?反轉的是什么?控制的是一個對象的創建、初始化和銷毀,沒有使用IOC之前,我們使用一個對象,會主動的去創建對象,也就是在需要使用這個對象的地方去執行A a = new A();
類似這種語句,而使用了IOC之后,會把系統中所有需要使用到的對象交給IOC容器去托管,當我們的程序中需要使用到某個對象時,也不是我們直接去容器獲取這個對象,而是容器會主動的給我們這個對象,也就是說IOC容器會自動給程序中需要對象的地方給你注入對象,這就是反轉,把獲取對象的行為由主動變為被動,并且把控制權交給了容器。
DI代表的是依賴注入,什么是依賴注入?我們都知道,對象與對象之間一般都會有依賴關系的,例如對象A依賴于對象B,對象B又依賴于對象C,舉個例子,對象A中有個方法a,a方法的實現需要調用B對象的b方法,這就是依賴,引入IOC之前,我們會在A對象中new一個B對象,再通過B對象去調用b方法;引入IOC之后,我們都不會去主動創建對象了,都交給IOC容器去處理了,我們只需要在對象A中聲明一個B對象的成員變量,加上xml配置或者相關注解,IOC容器就可以我們自動實現注入。例如下面這段代碼,在MVC架構中,Controller層依賴Service層,我們只需要在Controller層聲明一個Service層的變量,并加上@Autowired注解,當IOC容器啟動時加載ApplicationController這個Bean到容器中時,會自動把ApplicationService這個Bean也注入進來。
依賴注入的實現方式有三種:構造函數注入、set方法注入以及接口注入;接口注入用的比較少,一般都是使用構造函數注入和set方法注入,另外還有一種字段注入,如上圖所示的注入方式就是字段注入,這種注入方式不是規范的,可以看到@Autowired注解會給出警告,不建議我們使用這種方式,但這種方式卻是使用最多的,因為它很方便。
以下是構造函數注入:
以下是set方法注入:
2. Spring容器加載Bean/創建對象的時機
默認情況下,程序啟動的時候,Spring就會把程序中需要用到的Bean加載到IOC容器中,也就是會一次性創建很多的對象到IOC容器中。當然,還有一種方式,就是當需要使用到某個Bean的時候,再去創建這個Bean到容器中,這就是延遲加載,或者叫延遲實例化。
如果我們希望某個Bean不要過早的被加載到容器中,使用到的時候再去創建,可以給這個Bean加上@Lazy注解。
關于@Lazy注解實現延遲加載,需要注意一個細節,我們給某個Bean加上@Lazy注解之后,并不一定就能實現延遲加載,有可能程序在啟動的時候,這個Bean還是會在一開始就被創建,會產生這種情況是因為這個Bean有可能在別處被引用到,也就是說別的Bean可能依賴了這個Bean,而依賴的這個Bean又沒有打上@Lazy注解,那么程序啟動的時候,創建這個依賴的Bean,也會把它所依賴的Bean一并注入,這就是導致@Lazy注解有時沒有生效的原因。
舉個例子,ApplicationController這個Bean依賴了ApplicationServiceImpl,我們只給ApplicationServiceImpl這個Bean加上@Lazy注解,是無法實現延遲加載的,需要ApplicationController這個Bean也加上@Lazy注解。
3. @Autowired注解
@Autowired注解的作用就是實現依賴注入,大部分情況下,我們使用@Autowired這個注解就可以滿足我們的需求了,但是我們需要知道這個注解的約定是什么,才能更好的使用它。首先,@Autowired注解實現的是按照類型注入,補充一個點:為了滿足ocp原則,使得代碼更加靈活便于擴展,我們倡導面向接口編程。例如下面這個例子,ApplicationService是一個接口,我們加上了@Autowired注解,按照類型注入的原則,IOC容器會去找ApplicationService接口的實現類去進行依賴注入,如果這個接口只有一個實現類,那么沒有問題,可以實現注入,如果這個接口有多個實現類,那么IOC容器會知道給我們注入哪一個實現類嗎,顯然是不知道的,但是@Autowired還有一種規則,就是根據類型匹配時,如果能匹配多個,會再根據Bean的名稱去匹配,如果Bean的名稱能匹配上,那么沒有問題,可以實現注入;如果Bean的名稱匹配不上,程序會報錯,因為IOC容器不知道給我們注入哪一個ApplicationService接口的實現類。
舉個例子,假設現在ApplicationService接口有兩個實現類ApplicationServiceImpl1(Bean名稱:applicationServiceImpl1)和ApplicationServiceImpl2(Bean名稱:applicationServiceImpl2),如果@Autowired注解作用的代碼是private ApplicationService applicationServiceImpl1;,那么注入的是ApplicationServiceImpl1這個Bean,如果@Autowired注解作用的代碼是private ApplicationService applicationServiceImpl2;,那么注入的是ApplicationServiceImpl2這個Bean,如果@Autowired注解作用的代碼是private ApplicationService applicationServiceImpl;,那么程序報錯,IOC容器知道為我們注入哪個Bean。
如果涉及一個接口有多個實現類的情況,我們建議使用@Autowired注解搭配@Qualifier注解,@Qualifier注解的實現機制是按照Bean的名稱進行注入。
例如下面這個例子,首先@Autowired注解會根據類型找到ApplicationService的實現類,@Qualifier注解再找到名稱為applicationServiceImpl的Bean進行注入。
PS:上面我們講的是@Autowired注解如果匹配到多個Bean不知道注入哪一個的時候會報錯,還有一種情況,如果@Autowired注解匹配不到任何對應的Bean也會報錯。
4. @Configuration配置類
一個類被加上了@Configuration注解,就表明這個類是一個配置類,@Configuration注解底層注解是@Component,也就是說,注解類也會被加載到IOC容器中,在注解類中,通常會聲明很多方法,這些方法的返回值是一個對象,并且方法加上了@Bean注解,這表明會將這些方法返回的對象加載到IOC容器中。例如:
@Configuration注解是用來替換Bean的xml配置,可以把@Configuration看作標簽,把@Bean注解看作標簽
傳統的xml配置是這樣的:
<beans> <bean id = "people", class = "com.test.People"> </bean> <bean id = "student", class = "com.test.Student"> </bean></beans>
思考一個問題:一般來說,把一個Bean加載到IOC容器中,不是通過@Component注解就可以實現嗎?況且@Configuration注解的底層注解還是@Component,那為什么還需要@Configuation+@Bean這套注解來加載某些Bean到IOC容器中呢?換言之,@Configuation+@Bean的真正作用是什么?
@Configuation+@Bean的作用就是可以將一些定制化的Bean(特殊的Bean)注入到IOC容器中,什么是定制化的Bean?舉個例子,有一個People類,有兩個屬性name和age,我們希望把People類加載到IOC容器中,并且使得IOC容器的這個Bean的name屬性值是小明,age屬性值是18。如果只是在People類上加@Component注解,是無法實現我們這個定制化的Bean的,因為IOC容器在啟動的時候,創建Bean,默認調用的都是某個類的無參構造函數(也存在調用帶參構造函數的情況,但是這種情況使用帶參構造函數,只是為了實現依賴注入,也就是構造函數注入,并非是給屬性賦值),也就是說無法給我們的屬性賦特殊的值,而使用@Configuation+@Bean就可以實現。例如:
通常來說,我們不會直接把屬性值寫死在代碼里,而是通過讀取配置文件的形式,雖然說@Configuration注解表明這個類已經是一個配置類了,可以充當配置文件來使用,但是對于一些變化的屬性,我們都會定義在普通配置文件里面,例如application.properties。
5. @Conditional條件注解
@Conditional注解的作用是滿足某個條件時,才會將這個Bean注入到IOC容器中。我們可以通過自定義一個Condition類的方式定制某個規則,當滿足這個規則才會注入Bean,例如:
通常,我們直接使用一些成品條件注解就可以滿足我們的需求了。常用成品條件注解如下:
6. SpringBoot的自動配置/自動裝配
自動裝配是SpringBoot最核心的東西,學習自動裝配,我們首先應該了解自動裝配是什么,做了哪些事情?其次需要明確自動裝配存在的意義,也就是說自動裝配的好處是什么?最后再去了解自動裝配的原理。按照這個流程下來,才能更好的掌握自動裝配這個知識點。
a.自動裝配做了什么事情?就是SpringBoot會自動把一些第三方的庫或者SDK需要使用到的很多Bean都自動幫我們加載到IOC容器中。
b.自動裝配的好處是什么?基于IOC思想,任何程序中使用到的Bean都需要注入IOC容器中進行托管,也就是說,我們引用一些第三方的庫/依賴,例如Mongodb、Redis、Hadoop,也是需要把這些第三方的庫涉及的Bean都加載到IOC容器中的。如果沒有自動裝配,那么如何把第三方的庫里面的很多Bean都加載到我們的IOC容器中來,給每一個相關的類打上@Component注解?不是這樣的,我們所引用的第三方庫一般都是以jar包的形式存在,并不是直接給源碼。即便是可以通過打上@Component注解的方式,試想我們的工作量會增加多少,引用一個第三方庫可能涉及很多的Bean,并且有些Bean又可能是比較復雜的,我們不可能手動的將他們一個個注入容器,而SpringBoot都幫我們做好了,這就是自動裝配的好處。
c.自動裝配的原理
首先,在SpringBoot程序的主啟動類上,有一個非常重要的注解:@SpringBootApplication,進入到這個注解,可以發現底層由三個注解組成(元注解除外)
@SpringBootConfiguration
:表明SpringBoot程序的主啟動類也是一個配置類
@ComponentScan
:指定包掃描路徑,程序啟動時,會根據指定的包掃描路徑去加載Bean
@EnableAutoConfiguration
:最重要的一個注解,我們可以理解成這個注解的作用是開啟自動裝配
進入到@EnableAutoConfiguration,可以發現底層由兩個注解組成(元注解除外)
我們主要研究@Import注解,這個注解有兩種實現方式,第一種是指定一個或多個配置類,例如:@Import(TetsConfiguration.class)那么IOC容器就會將這個配置類加載到容器中來,另一種實現方式是指定一個ImportSelector類的子類,例如@Import(AutoConfigurationImportSelector.class)這個ImportSelector類的子類會去實現selectImports方法,該方法的返回值是一個字符串數組,代表要加載的配置類名稱列表。
SpringBoot自動裝配默認使用第二種方式,我們進到AutoConfigurationImportSelector這個類里面,發現它確實實現了selectImports方法。通過這個方法,SpringBoot就可以知道我們需要自動裝配哪些配置類
我們再進入getAutoConfigurationEntry方法,這個方法里面有一個核心方法getCandidateConfigurations,翻譯過來就是獲取候選的配置類
getCandidateConfigurations方法實現邏輯就是:會去讀取某一個配置文件,根據這個配置文件的值返回自動裝配要裝配的配置類名稱列表
我們進入到SpringFactoriesLoader類,可以知道getCandidateConfigurations讀取的配置文件就是META-INF下面的spring.factories
spring.factories的位置
spring.factories配置文件主要就聲明了很多自動配置類的名稱,截取部分如下:
總結一下,我們一路追蹤源碼下來,無非確定了一件事,SpringBoot自動裝配時裝配了哪些配置類,這些配置類的定義都存在于spring.factories配置文件中。
另外,我們需要明確,SpringBoot自動裝配時也不是把所有自動配置類定義的Bean都加載到IOC容器中,因為這些Bean大部分都會加上條件注解,需要滿足一定的條件才會被加載,例如,我們進入某一個自動配置類看一下
總結
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注服務器之家的更多內容!
原文鏈接:https://blog.csdn.net/can_chen/article/details/120614018