Java SpringBoot的相關(guān)知識(shí)點(diǎn)詳解
1. IOC和DI
首先,我們應(yīng)該明確,IOC是一種思想,并不是Spring特有的,而是軟件工程逐步發(fā)展的一種產(chǎn)物,是一種優(yōu)秀的編程思想,之所以我們經(jīng)常會(huì)把IOC理解成是Spring特有的東西,是因?yàn)镾pring框架可以幫助我們很好的去實(shí)現(xiàn)IOC。IOC代表的是控制反轉(zhuǎn),控制的是什么?反轉(zhuǎn)的是什么?控制的是一個(gè)對(duì)象的創(chuàng)建、初始化和銷毀,沒(méi)有使用IOC之前,我們使用一個(gè)對(duì)象,會(huì)主動(dòng)的去創(chuàng)建對(duì)象,也就是在需要使用這個(gè)對(duì)象的地方去執(zhí)行 A a = new A(); 類似這種語(yǔ)句,而使用了IOC之后,會(huì)把系統(tǒng)中所有需要使用到的對(duì)象交給IOC容器去托管,當(dāng)我們的程序中需要使用到某個(gè)對(duì)象時(shí),也不是我們直接去容器獲取這個(gè)對(duì)象,而是容器會(huì)主動(dòng)的給我們這個(gè)對(duì)象,也就是說(shuō)IOC容器會(huì)自動(dòng)給程序中需要對(duì)象的地方給你注入對(duì)象,這就是反轉(zhuǎn),把獲取對(duì)象的行為由主動(dòng)變?yōu)楸粍?dòng),并且把控制權(quán)交給了容器。
DI代表的是依賴注入,什么是依賴注入?我們都知道,對(duì)象與對(duì)象之間一般都會(huì)有依賴關(guān)系的,例如對(duì)象A依賴于對(duì)象B,對(duì)象B又依賴于對(duì)象C,舉個(gè)例子,對(duì)象A中有個(gè)方法a,a方法的實(shí)現(xiàn)需要調(diào)用B對(duì)象的b方法,這就是依賴,引入IOC之前,我們會(huì)在A對(duì)象中new一個(gè)B對(duì)象,再通過(guò)B對(duì)象去調(diào)用b方法;引入IOC之后,我們都不會(huì)去主動(dòng)創(chuàng)建對(duì)象了,都交給IOC容器去處理了,我們只需要在對(duì)象A中聲明一個(gè)B對(duì)象的成員變量,加上xml配置或者相關(guān)注解,IOC容器就可以我們自動(dòng)實(shí)現(xiàn)注入。例如下面這段代碼,在MVC架構(gòu)中,Controller層依賴Service層,我們只需要在Controller層聲明一個(gè)Service層的變量,并加上@Autowired注解,當(dāng)IOC容器啟動(dòng)時(shí)加載ApplicationController這個(gè)Bean到容器中時(shí),會(huì)自動(dòng)把ApplicationService這個(gè)Bean也注入進(jìn)來(lái)。

依賴注入的實(shí)現(xiàn)方式有三種:構(gòu)造函數(shù)注入、set方法注入以及接口注入;接口注入用的比較少,一般都是使用構(gòu)造函數(shù)注入和set方法注入,另外還有一種字段注入,如上圖所示的注入方式就是字段注入,這種注入方式不是規(guī)范的,可以看到@Autowired注解會(huì)給出警告,不建議我們使用這種方式,但這種方式卻是使用最多的,因?yàn)樗芊奖恪?br />

以下是構(gòu)造函數(shù)注入:

以下是set方法注入:

2. Spring容器加載Bean/創(chuàng)建對(duì)象的時(shí)機(jī)
默認(rèn)情況下,程序啟動(dòng)的時(shí)候,Spring就會(huì)把程序中需要用到的Bean加載到IOC容器中,也就是會(huì)一次性創(chuàng)建很多的對(duì)象到IOC容器中。當(dāng)然,還有一種方式,就是當(dāng)需要使用到某個(gè)Bean的時(shí)候,再去創(chuàng)建這個(gè)Bean到容器中,這就是延遲加載,或者叫延遲實(shí)例化。
如果我們希望某個(gè)Bean不要過(guò)早的被加載到容器中,使用到的時(shí)候再去創(chuàng)建,可以給這個(gè)Bean加上@Lazy注解。

關(guān)于@Lazy注解實(shí)現(xiàn)延遲加載,需要注意一個(gè)細(xì)節(jié),我們給某個(gè)Bean加上@Lazy注解之后,并不一定就能實(shí)現(xiàn)延遲加載,有可能程序在啟動(dòng)的時(shí)候,這個(gè)Bean還是會(huì)在一開始就被創(chuàng)建,會(huì)產(chǎn)生這種情況是因?yàn)檫@個(gè)Bean有可能在別處被引用到,也就是說(shuō)別的Bean可能依賴了這個(gè)Bean,而依賴的這個(gè)Bean又沒(méi)有打上@Lazy注解,那么程序啟動(dòng)的時(shí)候,創(chuàng)建這個(gè)依賴的Bean,也會(huì)把它所依賴的Bean一并注入,這就是導(dǎo)致@Lazy注解有時(shí)沒(méi)有生效的原因。
舉個(gè)例子,ApplicationController這個(gè)Bean依賴了ApplicationServiceImpl,我們只給ApplicationServiceImpl這個(gè)Bean加上@Lazy注解,是無(wú)法實(shí)現(xiàn)延遲加載的,需要ApplicationController這個(gè)Bean也加上@Lazy注解。



3. @Autowired注解
@Autowired注解的作用就是實(shí)現(xiàn)依賴注入,大部分情況下,我們使用@Autowired這個(gè)注解就可以滿足我們的需求了,但是我們需要知道這個(gè)注解的約定是什么,才能更好的使用它。首先,@Autowired注解實(shí)現(xiàn)的是按照類型注入,補(bǔ)充一個(gè)點(diǎn):為了滿足ocp原則,使得代碼更加靈活便于擴(kuò)展,我們倡導(dǎo)面向接口編程。例如下面這個(gè)例子,ApplicationService是一個(gè)接口,我們加上了@Autowired注解,按照類型注入的原則,IOC容器會(huì)去找ApplicationService接口的實(shí)現(xiàn)類去進(jìn)行依賴注入,如果這個(gè)接口只有一個(gè)實(shí)現(xiàn)類,那么沒(méi)有問(wèn)題,可以實(shí)現(xiàn)注入,如果這個(gè)接口有多個(gè)實(shí)現(xiàn)類,那么IOC容器會(huì)知道給我們注入哪一個(gè)實(shí)現(xiàn)類嗎,顯然是不知道的,但是@Autowired還有一種規(guī)則,就是根據(jù)類型匹配時(shí),如果能匹配多個(gè),會(huì)再根據(jù)Bean的名稱去匹配,如果Bean的名稱能匹配上,那么沒(méi)有問(wèn)題,可以實(shí)現(xiàn)注入;如果Bean的名稱匹配不上,程序會(huì)報(bào)錯(cuò),因?yàn)镮OC容器不知道給我們注入哪一個(gè)ApplicationService接口的實(shí)現(xiàn)類。

舉個(gè)例子,假設(shè)現(xiàn)在ApplicationService接口有兩個(gè)實(shí)現(xiàn)類ApplicationServiceImpl1(Bean名稱:applicationServiceImpl1)和ApplicationServiceImpl2(Bean名稱:applicationServiceImpl2),如果@Autowired注解作用的代碼是private ApplicationService applicationServiceImpl1;,那么注入的是ApplicationServiceImpl1這個(gè)Bean,如果@Autowired注解作用的代碼是private ApplicationService applicationServiceImpl2;,那么注入的是ApplicationServiceImpl2這個(gè)Bean,如果@Autowired注解作用的代碼是private ApplicationService applicationServiceImpl;,那么程序報(bào)錯(cuò),IOC容器知道為我們注入哪個(gè)Bean。
如果涉及一個(gè)接口有多個(gè)實(shí)現(xiàn)類的情況,我們建議使用@Autowired注解搭配@Qualifier注解,@Qualifier注解的實(shí)現(xiàn)機(jī)制是按照Bean的名稱進(jìn)行注入。
例如下面這個(gè)例子,首先@Autowired注解會(huì)根據(jù)類型找到ApplicationService的實(shí)現(xiàn)類,@Qualifier注解再找到名稱為applicationServiceImpl的Bean進(jìn)行注入。

PS:上面我們講的是@Autowired注解如果匹配到多個(gè)Bean不知道注入哪一個(gè)的時(shí)候會(huì)報(bào)錯(cuò),還有一種情況,如果@Autowired注解匹配不到任何對(duì)應(yīng)的Bean也會(huì)報(bào)錯(cuò)。
4. @Configuration配置類
一個(gè)類被加上了@Configuration注解,就表明這個(gè)類是一個(gè)配置類,@Configuration注解底層注解是@Component,也就是說(shuō),注解類也會(huì)被加載到IOC容器中,在注解類中,通常會(huì)聲明很多方法,這些方法的返回值是一個(gè)對(duì)象,并且方法加上了@Bean注解,這表明會(huì)將這些方法返回的對(duì)象加載到IOC容器中。例如:

@Configuration注解是用來(lái)替換Bean的xml配置,可以把@Configuration看作標(biāo)簽,把@Bean注解看作標(biāo)簽
傳統(tǒng)的xml配置是這樣的:
<beans> <bean id = "people", class = "com.test.People"> </bean> <bean id = "student", class = "com.test.Student"> </bean></beans>
思考一個(gè)問(wèn)題:一般來(lái)說(shuō),把一個(gè)Bean加載到IOC容器中,不是通過(guò)@Component注解就可以實(shí)現(xiàn)嗎?況且@Configuration注解的底層注解還是@Component,那為什么還需要@Configuation+@Bean這套注解來(lái)加載某些Bean到IOC容器中呢?換言之,@Configuation+@Bean的真正作用是什么?
@Configuation+@Bean的作用就是可以將一些定制化的Bean(特殊的Bean)注入到IOC容器中,什么是定制化的Bean?舉個(gè)例子,有一個(gè)People類,有兩個(gè)屬性name和age,我們希望把People類加載到IOC容器中,并且使得IOC容器的這個(gè)Bean的name屬性值是小明,age屬性值是18。如果只是在People類上加@Component注解,是無(wú)法實(shí)現(xiàn)我們這個(gè)定制化的Bean的,因?yàn)镮OC容器在啟動(dòng)的時(shí)候,創(chuàng)建Bean,默認(rèn)調(diào)用的都是某個(gè)類的無(wú)參構(gòu)造函數(shù)(也存在調(diào)用帶參構(gòu)造函數(shù)的情況,但是這種情況使用帶參構(gòu)造函數(shù),只是為了實(shí)現(xiàn)依賴注入,也就是構(gòu)造函數(shù)注入,并非是給屬性賦值),也就是說(shuō)無(wú)法給我們的屬性賦特殊的值,而使用@Configuation+@Bean就可以實(shí)現(xiàn)。例如:


通常來(lái)說(shuō),我們不會(huì)直接把屬性值寫死在代碼里,而是通過(guò)讀取配置文件的形式,雖然說(shuō)@Configuration注解表明這個(gè)類已經(jīng)是一個(gè)配置類了,可以充當(dāng)配置文件來(lái)使用,但是對(duì)于一些變化的屬性,我們都會(huì)定義在普通配置文件里面,例如application.properties。


5. @Conditional條件注解
@Conditional注解的作用是滿足某個(gè)條件時(shí),才會(huì)將這個(gè)Bean注入到IOC容器中。我們可以通過(guò)自定義一個(gè)Condition類的方式定制某個(gè)規(guī)則,當(dāng)滿足這個(gè)規(guī)則才會(huì)注入Bean,例如:


通常,我們直接使用一些成品條件注解就可以滿足我們的需求了。常用成品條件注解如下:

6. SpringBoot的自動(dòng)配置/自動(dòng)裝配
自動(dòng)裝配是SpringBoot最核心的東西,學(xué)習(xí)自動(dòng)裝配,我們首先應(yīng)該了解自動(dòng)裝配是什么,做了哪些事情?其次需要明確自動(dòng)裝配存在的意義,也就是說(shuō)自動(dòng)裝配的好處是什么?最后再去了解自動(dòng)裝配的原理。按照這個(gè)流程下來(lái),才能更好的掌握自動(dòng)裝配這個(gè)知識(shí)點(diǎn)。
a. 自動(dòng)裝配做了什么事情? 就是SpringBoot會(huì)自動(dòng)把一些第三方的庫(kù)或者SDK需要使用到的很多Bean都自動(dòng)幫我們加載到IOC容器中。
b. 自動(dòng)裝配的好處是什么? 基于IOC思想,任何程序中使用到的Bean都需要注入IOC容器中進(jìn)行托管,也就是說(shuō),我們引用一些第三方的庫(kù)/依賴,例如Mongodb、Redis、Hadoop,也是需要把這些第三方的庫(kù)涉及的Bean都加載到IOC容器中的。如果沒(méi)有自動(dòng)裝配,那么如何把第三方的庫(kù)里面的很多Bean都加載到我們的IOC容器中來(lái),給每一個(gè)相關(guān)的類打上@Component注解?不是這樣的,我們所引用的第三方庫(kù)一般都是以jar包的形式存在,并不是直接給源碼。即便是可以通過(guò)打上@Component注解的方式,試想我們的工作量會(huì)增加多少,引用一個(gè)第三方庫(kù)可能涉及很多的Bean,并且有些Bean又可能是比較復(fù)雜的,我們不可能手動(dòng)的將他們一個(gè)個(gè)注入容器,而SpringBoot都幫我們做好了,這就是自動(dòng)裝配的好處。
c. 自動(dòng)裝配的原理
首先,在SpringBoot程序的主啟動(dòng)類上,有一個(gè)非常重要的注解:@SpringBootApplication,進(jìn)入到這個(gè)注解,可以發(fā)現(xiàn)底層由三個(gè)注解組成(元注解除外)

@SpringBootConfiguration:表明SpringBoot程序的主啟動(dòng)類也是一個(gè)配置類
@ComponentScan:指定包掃描路徑,程序啟動(dòng)時(shí),會(huì)根據(jù)指定的包掃描路徑去加載Bean
@EnableAutoConfiguration:最重要的一個(gè)注解,我們可以理解成這個(gè)注解的作用是開啟自動(dòng)裝配
進(jìn)入到@EnableAutoConfiguration,可以發(fā)現(xiàn)底層由兩個(gè)注解組成(元注解除外)

我們主要研究@Import注解,這個(gè)注解有兩種實(shí)現(xiàn)方式,第一種是指定一個(gè)或多個(gè)配置類,例如:@Import(TetsConfiguration.class) 那么IOC容器就會(huì)將這個(gè)配置類加載到容器中來(lái),另一種實(shí)現(xiàn)方式是指定一個(gè)ImportSelector類的子類,例如@Import(AutoConfigurationImportSelector.class) 這個(gè)ImportSelector類的子類會(huì)去實(shí)現(xiàn)selectImports方法,該方法的返回值是一個(gè)字符串?dāng)?shù)組,代表要加載的配置類名稱列表。
SpringBoot自動(dòng)裝配默認(rèn)使用第二種方式,我們進(jìn)到AutoConfigurationImportSelector這個(gè)類里面,發(fā)現(xiàn)它確實(shí)實(shí)現(xiàn)了selectImports方法。通過(guò)這個(gè)方法,SpringBoot就可以知道我們需要自動(dòng)裝配哪些配置類

我們?cè)龠M(jìn)入getAutoConfigurationEntry方法,這個(gè)方法里面有一個(gè)核心方法getCandidateConfigurations,翻譯過(guò)來(lái)就是獲取候選的配置類

getCandidateConfigurations方法實(shí)現(xiàn)邏輯就是:會(huì)去讀取某一個(gè)配置文件,根據(jù)這個(gè)配置文件的值返回自動(dòng)裝配要裝配的配置類名稱列表

我們進(jìn)入到SpringFactoriesLoader類,可以知道getCandidateConfigurations讀取的配置文件就是META-INF下面的spring.factories

spring.factories的位置

spring.factories配置文件主要就聲明了很多自動(dòng)配置類的名稱,截取部分如下:

總結(jié)一下,我們一路追蹤源碼下來(lái),無(wú)非確定了一件事,SpringBoot自動(dòng)裝配時(shí)裝配了哪些配置類,這些配置類的定義都存在于spring.factories配置文件中。
另外,我們需要明確,SpringBoot自動(dòng)裝配時(shí)也不是把所有自動(dòng)配置類定義的Bean都加載到IOC容器中,因?yàn)檫@些Bean大部分都會(huì)加上條件注解,需要滿足一定的條件才會(huì)被加載,例如,我們進(jìn)入某一個(gè)自動(dòng)配置類看一下

總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Spring學(xué)習(xí)筆記之RedisTemplate的配置與使用教程
這篇文章主要給大家介紹了關(guān)于Spring學(xué)習(xí)筆記之RedisTemplate配置與使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06
解決idea中@Data標(biāo)簽getset不起作用的問(wèn)題
這篇文章主要介紹了解決idea中@Data標(biāo)簽getset不起作用的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02
淺談java運(yùn)用注解實(shí)現(xiàn)對(duì)類中的方法檢測(cè)的工具
這篇文章主要介紹了淺談java運(yùn)用注解實(shí)現(xiàn)對(duì)類中的方法檢測(cè)的工具,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
分享Java性能調(diào)優(yōu)的11個(gè)實(shí)用技巧
這些建議中的大多數(shù)都是基于Java的,但是也不一定,也有一些是可以應(yīng)用于所有的應(yīng)用程序和編程語(yǔ)言的。在我們分享基于Java的性能調(diào)優(yōu)技巧之前,讓我們先討論一下這些通用的性能調(diào)優(yōu)技巧2017-11-11
Java 線程的優(yōu)先級(jí)(setPriority)案例詳解
這篇文章主要介紹了Java 線程的優(yōu)先級(jí)(setPriority)案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
SpringBoot自動(dòng)裝配之@Import深入講解
由于最近的項(xiàng)目需求,需要在把配置類導(dǎo)入到容器中,通過(guò)查詢,使用@Import注解就能實(shí)現(xiàn)這個(gè)功能,@Import注解能夠幫我們吧普通配置類(定義為Bean的類)導(dǎo)入到IOC容器中2023-01-01
繼承jpa?Repository?寫自定義方法查詢實(shí)例
這篇文章主要介紹了繼承jpa?Repository?寫自定義方法查詢實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java通過(guò)Lambda函數(shù)的方式獲取屬性名稱
這篇文章主要介紹了通過(guò)Lambda函數(shù)的方式獲取屬性名稱,實(shí)現(xiàn)步驟是通過(guò)定義一個(gè)函數(shù)式接口, 用來(lái)接收l(shuí)ambda方法引用,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10

