SpringBoot實(shí)現(xiàn)自動(dòng)配置的示例代碼
什么是自動(dòng)配置?
Spring Boot 的自動(dòng)配置:當(dāng) Spring 容器啟動(dòng)后,一些配置類、bean 對(duì)象等就自動(dòng)存入 Ioc 容器中,而不再需要我們手動(dòng)去聲明,從而簡(jiǎn)化了程序開發(fā)過程,省去了繁瑣的配置操作
也就是說(shuō),Spring Boot 的自動(dòng)配置,就是 SpinrgBoot 將依賴 jar 包中的配置類以及 Bean 加載到 Spring Ioc 容器中的過程
在本篇文章中,我們主要學(xué)習(xí)一下兩個(gè)方面:
1. Spring 如何將對(duì)象加載到 Spring Ioc 容器中
2. SpringBoot 是如何進(jìn)行實(shí)現(xiàn)的
我們首先來(lái)看 Spring 是如何加載 Bean 的
Spring 加載 Bean
當(dāng)我們?cè)陧?xiàng)目中引入第三方的包時(shí),其實(shí)就是在該項(xiàng)目下引入第三方的代碼,我們通過在該項(xiàng)目下創(chuàng)建不同的目錄來(lái)模擬第三方代碼的引入:
當(dāng)前項(xiàng)目目錄為 com.example.springautoconfig,模擬第三方代碼文件在 com.example.autoconfig 目錄下
第三方文件代碼:
@Component public class AutoConfig { public void test() { System.out.println("test..."); } }
獲取 AutoConfig:
@SpringBootTest class SpringAutoconfigApplicationTests { @Autowired private ApplicationContext context; @Test void contextLoads() { AutoConfig bean = context.getBean(AutoConfig.class); System.out.println(bean); } }
運(yùn)行結(jié)果:
此時(shí)顯示沒有 com.example.autoconfig.AutoConfig 這個(gè)類型的 bean
為什么會(huì)報(bào)錯(cuò)呢?
Spring 使用 類注解(@Controller、@Service、@Repository、@Component、@Configuration) 和 @Bean 注解 幫助我們將 Bean 加載到 Spring Ioc 容器中時(shí),有一個(gè)前提:這些注解需要和 SpringBoot 啟動(dòng)類(@SpringBootApplication 標(biāo)注的類)在同一個(gè)目錄下
而在上述項(xiàng)目中,啟動(dòng)類所在目錄為 com.example.springautoconfig,而 AutoConfig 類位于 com.example.autoconfig 目錄下,因此,SpringBoot 并沒有掃描到
可是,當(dāng)我們引入第三方的 jar 包時(shí),第三方的 jar 代碼目錄也不在啟動(dòng)類的目錄下,那么,如何讓 Spring 幫我們管理這些 Bean 的呢?
我們可以通過指定路徑或引入的文件告訴 Spring,讓 Spring 進(jìn)行掃描
常見的實(shí)現(xiàn)方法有兩種:
1. @ComponentScan 組件掃描
2. @Import 導(dǎo)入
@ComponentScan
使用 @ComponentScan 注解,指定 Spring 掃描路徑:
@SpringBootApplication @ComponentScan("com.example.autoconfig") public class SpringAutoconfigApplication { public static void main(String[] args) { SpringApplication.run(SpringAutoconfigApplication.class, args); } }
運(yùn)行程序并觀察結(jié)果:
成功獲取到 AutoConfig bean
那么,Spring Boot 是否使用了這種方式呢?
顯然沒有。若 Spring Boot 采用這種方式,當(dāng)我們引入大量的第三方依賴,如 MyBatis、jackson 等時(shí),就需要在啟動(dòng)類上配置不同依賴需要掃描的包,非常繁瑣
@Import
@Import 導(dǎo)入主要有以下幾種形式:
1. 導(dǎo)入類
2. 導(dǎo)入 ImportSelector 接口的實(shí)現(xiàn)類
導(dǎo)入類
@Import(AutoConfig.class) @SpringBootApplication public class SpringAutoconfigApplication { public static void main(String[] args) { SpringApplication.run(SpringAutoconfigApplication.class, args); } }
運(yùn)行程序,觀察結(jié)果:
成功獲取到 AutoConfig Bean
若在文件中有多個(gè)配置項(xiàng):
@Component public class AutoConfig2 { public void test() { System.out.println("test..."); } }
此時(shí)就需要導(dǎo)入多個(gè)類:
@Import({AutoConfig.class, AutoConfig2.class}) @SpringBootApplication public class SpringAutoconfigApplication { public static void main(String[] args) { SpringApplication.run(SpringAutoconfigApplication.class, args); } }
顯而易見,這種方式也比較繁瑣,因此,Spring Boot 也沒有采用
導(dǎo)入 ImportSelector 接口的實(shí)現(xiàn)類
實(shí)現(xiàn) ImportSelector 接口:
public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { // 返回需要導(dǎo)入的全限定類名 return new String[]{"com.example.autoconfig.AutoConfig", "com.example.autoconfig.AutoConfig2"}; } }
導(dǎo)入:
@Import(MyImportSelector.class) @SpringBootApplication public class SpringAutoconfigApplication { public static void main(String[] args) { SpringApplication.run(SpringAutoconfigApplication.class, args); } }
運(yùn)行程序,并觀察結(jié)果:
這種方式也可以導(dǎo)入第三方依賴提供的 Bean
但是,它們都有一個(gè)明顯的問題:使用者需要知道第三方依賴中有哪些 Bean 對(duì)象或配置類,若我們?cè)趯?dǎo)入過程中漏掉了一些 Bean,就可能會(huì)導(dǎo)致我們的項(xiàng)目出現(xiàn)問題
依賴中有哪些 Bean,使用時(shí)需要配置哪些 Bean,這些問題第三方依賴最為清楚,那么,能否由第三方依賴來(lái)做這些事情呢?
比較常見的方法是第三方依賴提供一個(gè)注解,而這個(gè)注解一般是以 @EnableXxx 開頭的注解,而注解中封裝的就是 @Import 注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) // 指定要導(dǎo)入哪些類 @Import(MyImportSelector.class) public @interface EnableAutoConfig { }
在啟動(dòng)類上使用第三方提供的注解:
@EnableAutoConfig @SpringBootApplication public class SpringAutoconfigApplication { public static void main(String[] args) { SpringApplication.run(SpringAutoconfigApplication.class, args); } }
運(yùn)行并觀察結(jié)果:
可以看到,這種方式也可以導(dǎo)入第三方依賴提供的 Bean,且這種方式不需要使用方知道第三方依賴中有哪些 Bean 對(duì)象或配置類,而在 Spring Boot 中也是使用的這種方式
SpringBoot 原理分析
SpringBoot 是如何進(jìn)行實(shí)現(xiàn)的?讓我們從 SpringBoot 的啟動(dòng)類開始看:
由 @SpringBootApplication 標(biāo)注的類就是 SpringBoot 項(xiàng)目的啟動(dòng)類:
這個(gè)類與普通類的唯一區(qū)別就是 @SpringBootApplication 注解,這個(gè)注解也是 SpringBoot 實(shí)現(xiàn)自動(dòng)配置的核心
@SpringBootApplication 是一個(gè)組合注解,注解中包含了:
1. 元注解:
JDK 中提供了 4 個(gè) 標(biāo)準(zhǔn)的用來(lái)對(duì)注解類型進(jìn)行注解的注解類,稱之為 meta-annotation(元注解)
分別為:
@Retention:描述注解保留的時(shí)間范圍
@Target:描述注解的使用范圍
@Documented:描述在使用 javadoc 工具為類生成幫助文檔時(shí)是否保留其注解信息
@Inherited:使被其修飾的注解具有繼承性(若某個(gè)類使用了 @Inherited,則其子類將自動(dòng)具有該注解)
2. @SpringBootConfiguration:
標(biāo)識(shí)當(dāng)前類是一個(gè)配置類,里面其實(shí)就是 @Configuration,只是做了進(jìn)一步的封裝
其中,@Indexed 注解是用來(lái)加速應(yīng)用啟動(dòng)的
3. @EnableAutoConfiguration(開啟自動(dòng)配置)
是 spiring 自動(dòng)配置的核心注解,我們?cè)诤罄m(xù)詳細(xì)理解
4. ComponentScan(包掃描)
可以通過 basePackageClasses 或 basePackages 來(lái)定義要掃描的特定包,若沒有定義特定的包,將從聲明該注解的類的包開始掃描,這也是 SpringBoot 項(xiàng)目聲明的注解類為什么必須在啟動(dòng)類目錄下
也可以自定義過濾器,用于排查一些類、注解等
接下來(lái),我們重點(diǎn)來(lái)看 @EnableAutoConfiguration(開啟自動(dòng)配置)
@EnableAutoConfiguration
@EnableAutoConfiguration 中主要包含兩部分:
@Import(AutoConfigurationImportSelector.class)
@AutoConfigurationPackage
我們先來(lái)看 @Import(AutoConfigurationImportSelector.class)
@Import(AutoConfigurationImportSelector.class)
使用 @Import 注解,導(dǎo)入了實(shí)現(xiàn) ImportSelector 接口的實(shí)現(xiàn)類:
在 selectImports() 方法中,調(diào)用了 getAutoConfigurationEntry() 方法,獲取可自動(dòng)配置的配置類信息
我們繼續(xù)看 getAutoConfigurationEntry() 方法:
在 getAutoConfigurationEntry() 方法中,主要通過 getCandidateConfigurations(annotationMetadata, attributes) 方法 和 fireAutoConfigurationImportEvents(configurations, exclusions) 方法,獲取在配置文件中配置的所有自動(dòng)配置類的集合
我們先看 getCandidateConfigurations(annotationMetadata, attributes) 方法:
在 getCandidateConfigurations(annotationMetadata, attributes) 方法中,使用 ImportCandidates 進(jìn)行加載
那么,從哪里進(jìn)行加載呢?
從 斷言 的錯(cuò)誤信息中我們可以找到答案:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
也就是說(shuō), getCandidateConfigurations 方法會(huì)獲取所有基于META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置類的集合
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件:
其中包含了很多第三方依賴的配置文件
我們以 redis 為例:
查看 RedisAutoConfiguration:
可以看到,在使用 redis 時(shí)常用的 RedisTemplate 和 StringRedisTemplate 就位于其中,在我們需要使用時(shí)直接使用 @Autowired 進(jìn)行注入就可以了
但是,由于當(dāng)前項(xiàng)目并沒有引入 redis 相關(guān) jar 包,此時(shí)這個(gè)類并不能被加載
也就是說(shuō)在加載自動(dòng)配置類的時(shí)候,并不是將所有的配置全部加載進(jìn)來(lái),而是會(huì)先進(jìn)行判斷,根據(jù) @ConditionalOnMissingBean 、@ConditionalOnSingleCandidate 等注解的判斷進(jìn)行動(dòng)態(tài)加載
即,在配置文件中使用 @Bean 聲明對(duì)象,spring 會(huì)自動(dòng)調(diào)用配置類中使用 @Bean 標(biāo)識(shí)的方法,并將對(duì)象存放到 Spring Ioc 中,但是,在加載自動(dòng)配置類的時(shí)候,并不是將所有的配置全部加載進(jìn)來(lái),而是會(huì)通過 @Conditional 等注解的判斷進(jìn)行動(dòng)態(tài)加載(@Conditional 是 spring 底層注解,會(huì)根據(jù)不同的條件,進(jìn)行條件判斷,若滿足指定條件,配置類中的配置才會(huì)生效)
我們繼續(xù)看 fireAutoConfigurationImportEvents 方法,找到是從哪里獲取配置類的:
可以看到,fireAutoConfigurationImportEvents 方法最終會(huì)從 META-INF/spring.factories 中獲取配置類的集合
我們來(lái)看 META-INF/spring.factories:
META-INF/spring.factories 文件是 Spring 內(nèi)部提供的一個(gè)約定俗稱的加載方式,只需要在模塊的 META-INF/spring.factories 文件中進(jìn)行配置,Spring 就會(huì)把相應(yīng)的實(shí)現(xiàn)類注入到 Spring 容器中
例如,有一個(gè)自動(dòng)配置類,希望 SpringBoot 在啟動(dòng)時(shí)自動(dòng)加載這個(gè)配置,我們就可以在 META-INF/spring.factories 文件按照指定格式進(jìn)行配置,讓 Spring Boot 應(yīng)用啟動(dòng)時(shí),讀取這個(gè) spring.factories 文件,根據(jù)文件中指定的配置類來(lái)進(jìn)行自動(dòng)配置
spring 會(huì)加載所有 jar 包下的 META-INF/spring.factories 文件
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 和 META-INF/spring.factories 都在引入的起步依賴中:
我們繼續(xù)看 AutoConfigurationPackage
AutoConfigurationPackage
在這個(gè)注解中,主要導(dǎo)入了一個(gè)配置文件 AutoConfigurationPackages.Registrar.class
我們繼續(xù)看 Registrar :
Registrar 實(shí)現(xiàn)了 ImportBeanDefinitionRegistrar 接口,可以被 @Import 導(dǎo)入到 spring 容器中
new PackageImports(metadata).getPackageNames().toArray(new String[0]): 當(dāng)前啟動(dòng)類所在的包名
也就是說(shuō),@AutoConfigurationPackage 的作用是 將啟動(dòng)類所在的包下面所有的組件都掃描注冊(cè)到 spring 容器中
最后,我們來(lái)總結(jié)一下 SpringBoot 自動(dòng)配置的流程
SpringBoot 自動(dòng)配置流程
SpringBoot 的自動(dòng)配置的入口是 @SpringBootApplication 注解,在這個(gè)注解中,主要封裝了 3 個(gè)注解:
@SpringBootConfiguration:標(biāo)識(shí)當(dāng)前類是配置類
@ComponentScan(包掃描):可以通過 basePackageClasses 或 basePackages 定義要掃描的特定包,若沒有定義特定的包,將從聲明該注解的類的包開始掃描,也就是說(shuō),默認(rèn)情況下掃描的是啟動(dòng)類所在的當(dāng)前包以及子包
@EnableAutoConfiguration(開啟自動(dòng)配置):主要包含兩部分:
@Import(AutoConfigurationImportSelector.class) :讀取 META-INF/spring.factories 和 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中定義的配置類????
@AutoConfigurationPackage:將啟動(dòng)類所在的包下所有組件都注入到 Spring 容器中
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)自動(dòng)配置的示例代碼的文章就介紹到這了,更多相關(guān)SpringBoot自動(dòng)配置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java調(diào)用瀏覽器打開網(wǎng)頁(yè)完整實(shí)例
這篇文章主要介紹了Java調(diào)用瀏覽器打開網(wǎng)頁(yè)的方法,以完整實(shí)例形式分析了java打開網(wǎng)頁(yè)的相關(guān)技巧,需要的朋友可以參考下2015-05-05springboot?vue測(cè)試平臺(tái)開發(fā)調(diào)通前后端環(huán)境實(shí)現(xiàn)登錄
這篇文章主要介紹了springboot?vue測(cè)試平臺(tái)開發(fā)調(diào)通前后端環(huán)境實(shí)現(xiàn)登錄詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Java中final、static關(guān)鍵字與方法的重寫和繼承易錯(cuò)點(diǎn)整理
這篇文章主要給大家介紹了關(guān)于Java中final、static關(guān)鍵字與方法的重寫和繼承易錯(cuò)點(diǎn)的相關(guān)資料,在Java編程中final關(guān)鍵字用于限制方法或類的進(jìn)一步修改,final方法不能被子類重寫,而static方法不可被重寫,只能被遮蔽,需要的朋友可以參考下2024-10-10Java獲取時(shí)間打印到控制臺(tái)代碼實(shí)例
這篇文章主要介紹了Java獲取時(shí)間打印到控制臺(tái)代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02