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