您好,登錄后才能下訂單哦!
這篇文章給大家介紹SpringBoot自動(dòng)裝配原理是什么,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
每次問(wèn)到 Spring Boot, 面試官非常喜歡問(wèn)這個(gè)問(wèn)題:“講述一下 SpringBoot 自動(dòng)裝配原理?”。
我覺(jué)得我們可以從以下幾個(gè)方面回答:
什么是 SpringBoot 自動(dòng)裝配?
SpringBoot 是如何實(shí)現(xiàn)自動(dòng)裝配的?如何實(shí)現(xiàn)按需加載?
如何實(shí)現(xiàn)一個(gè) Starter?
這篇文章并沒(méi)有深入,小伙伴們也可以直接使用 debug 的方式去看看 SpringBoot 自動(dòng)裝配部分的源代碼。
使用過(guò) Spring 的小伙伴,一定有被 XML 配置統(tǒng)治的恐懼。即使 Spring 后面引入了基于注解的配置,我們?cè)陂_啟某些 Spring 特性或者引入第三方依賴的時(shí)候,還是需要用 XML 或 Java 進(jìn)行顯式配置。
舉個(gè)例子。沒(méi)有 Spring Boot 的時(shí)候,我們寫一個(gè) RestFul Web 服務(wù),還首先需要進(jìn)行如下配置。
@Configuration public class RESTConfiguration { @Bean public View jsonTemplate() { MappingJackson2JsonView view = new MappingJackson2JsonView(); view.setPrettyPrint(true); return view; } @Bean public ViewResolver viewResolver() { return new BeanNameViewResolver(); } }
spring-servlet.xml
<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" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc/ http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package="com.howtodoinjava.demo" /> <mvc:annotation-driven /> <!-- JSON Support --> <bean name="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"/> <bean name="jsonTemplate" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/> </beans>
但是,Spring Boot 項(xiàng)目,我們只需要添加相關(guān)依賴,無(wú)需配置,通過(guò)啟動(dòng)下面的 main
方法即可。
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
并且,我們通過(guò) Spring Boot 的全局配置文件 application.properties
或application.yml
即可對(duì)項(xiàng)目進(jìn)行設(shè)置比如更換端口號(hào),配置 JPA 屬性等等。
為什么 Spring Boot 使用起來(lái)這么酸爽呢? 這得益于其自動(dòng)裝配。自動(dòng)裝配可以說(shuō)是 Spring Boot 的核心,那究竟什么是自動(dòng)裝配呢?
我們現(xiàn)在提到自動(dòng)裝配的時(shí)候,一般會(huì)和 Spring Boot 聯(lián)系在一起。但是,實(shí)際上 Spring Framework 早就實(shí)現(xiàn)了這個(gè)功能。Spring Boot 只是在其基礎(chǔ)上,通過(guò) SPI 的方式,做了進(jìn)一步優(yōu)化。
SpringBoot 定義了一套接口規(guī)范,這套規(guī)范規(guī)定:SpringBoot 在啟動(dòng)時(shí)會(huì)掃描外部引用 jar 包中的
META-INF/spring.factories
文件,將文件中配置的類型信息加載到 Spring 容器(此處涉及到 JVM 類加載機(jī)制與 Spring 的容器知識(shí)),并執(zhí)行類中定義的各種操作。對(duì)于外部 jar 來(lái)說(shuō),只需要按照 SpringBoot 定義的標(biāo)準(zhǔn),就能將自己的功能裝置進(jìn) SpringBoot。
沒(méi)有 Spring Boot 的情況下,如果我們需要引入第三方依賴,需要手動(dòng)配置,非常麻煩。但是,Spring Boot 中,我們直接引入一個(gè) starter 即可。比如你想要在項(xiàng)目中使用 redis 的話,直接在項(xiàng)目中引入對(duì)應(yīng)的 starter 即可。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
引入 starter 之后,我們通過(guò)少量注解和一些簡(jiǎn)單的配置就能使用第三方組件提供的功能了。
在我看來(lái),自動(dòng)裝配可以簡(jiǎn)單理解為:通過(guò)注解或者一些簡(jiǎn)單的配置就能在 Spring Boot 的幫助下實(shí)現(xiàn)某塊功能。
我們先看一下 SpringBoot 的核心注解 SpringBootApplication
。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited <1.>@SpringBootConfiguration <2.>@ComponentScan <3.>@EnableAutoConfiguration public @interface SpringBootApplication { } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration //實(shí)際上它也是一個(gè)配置類 public @interface SpringBootConfiguration { }
大概可以把 @SpringBootApplication
看作是 @Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。根據(jù) SpringBoot 官網(wǎng),這三個(gè)注解的作用分別是:
@EnableAutoConfiguration
:?jiǎn)⒂?SpringBoot 的自動(dòng)配置機(jī)制
@Configuration
:允許在上下文中注冊(cè)額外的 bean 或?qū)肫渌渲妙?/p>
@ComponentScan
: 掃描被@Component
(@Service
,@Controller
)注解的 bean,注解默認(rèn)會(huì)掃描啟動(dòng)類所在的包下所有的類 ,可以自定義不掃描某些 bean。如下圖所示,容器中將排除TypeExcludeFilter
和AutoConfigurationExcludeFilter
。
@EnableAutoConfiguration
是實(shí)現(xiàn)自動(dòng)裝配的重要注解,我們以這個(gè)注解入手。
EnableAutoConfiguration
只是一個(gè)簡(jiǎn)單地注解,自動(dòng)裝配核心功能的實(shí)現(xiàn)實(shí)際是通過(guò) AutoConfigurationImportSelector
類。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage //作用:將main包下的所欲組件注冊(cè)到容器中 @Import({AutoConfigurationImportSelector.class}) //加載自動(dòng)裝配類 xxxAutoconfiguration public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
我們現(xiàn)在重點(diǎn)分析下AutoConfigurationImportSelector
類到底做了什么?
AutoConfigurationImportSelector
類的繼承體系如下:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { } public interface DeferredImportSelector extends ImportSelector { } public interface ImportSelector { String[] selectImports(AnnotationMetadata var1); }
可以看出,AutoConfigurationImportSelector
類實(shí)現(xiàn)了 ImportSelector
接口,也就實(shí)現(xiàn)了這個(gè)接口中的 selectImports
方法,該方法主要用于獲取所有符合條件的類的全限定類名,這些類需要被加載到 IoC 容器中。
private static final String[] NO_IMPORTS = new String[0]; public String[] selectImports(AnnotationMetadata annotationMetadata) { // <1>.判斷自動(dòng)裝配開關(guān)是否打開 if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { //<2>.獲取所有需要裝配的bean AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } }
這里我們需要重點(diǎn)關(guān)注一下getAutoConfigurationEntry()
方法,這個(gè)方法主要負(fù)責(zé)加載自動(dòng)配置類的。
該方法調(diào)用鏈如下:
現(xiàn)在我們結(jié)合getAutoConfigurationEntry()
的源碼來(lái)詳細(xì)分析一下:
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry(); AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { //<1>. if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { //<2>. AnnotationAttributes attributes = this.getAttributes(annotationMetadata); //<3>. List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); //<4>. configurations = this.removeDuplicates(configurations); Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = this.filter(configurations, autoConfigurationMetadata); this.fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } }
第 1 步:
判斷自動(dòng)裝配開關(guān)是否打開。默認(rèn)spring.boot.enableautoconfiguration=true
,可在 application.properties
或 application.yml
中設(shè)置
第 2 步 :
用于獲取EnableAutoConfiguration
注解中的 exclude
和 excludeName
。
第 3 步
獲取需要自動(dòng)裝配的所有配置類,讀取META-INF/spring.factories
spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
從下圖可以看到這個(gè)文件的配置內(nèi)容都被我們讀取到了。XXXAutoConfiguration
的作用就是按需加載組件。
不光是這個(gè)依賴下的META-INF/spring.factories
被讀取到,所有 Spring Boot Starter 下的META-INF/spring.factories
都會(huì)被讀取到。
所以,你可以清楚滴看到, druid 數(shù)據(jù)庫(kù)連接池的 Spring Boot Starter 就創(chuàng)建了META-INF/spring.factories
文件。
如果,我們自己要?jiǎng)?chuàng)建一個(gè) Spring Boot Starter,這一步是必不可少的。
第 4 步 :
到這里可能面試官會(huì)問(wèn)你:“spring.factories
中這么多配置,每次啟動(dòng)都要全部加載么?”。
很明顯,這是不現(xiàn)實(shí)的。我們 debug 到后面你會(huì)發(fā)現(xiàn),configurations
的值變小了。
因?yàn)椋@一步有經(jīng)歷了一遍篩選,@ConditionalOnXXX
中的所有條件都滿足,該類才會(huì)生效。
@Configuration // 檢查相關(guān)的類:RabbitTemplate 和 Channel是否存在 // 存在才會(huì)加載 @ConditionalOnClass({ RabbitTemplate.class, Channel.class }) @EnableConfigurationProperties(RabbitProperties.class) @Import(RabbitAnnotationDrivenConfiguration.class) public class RabbitAutoConfiguration { }
有興趣的童鞋可以詳細(xì)了解下 Spring Boot 提供的條件注解
@ConditionalOnBean
:當(dāng)容器里有指定 Bean 的條件下
@ConditionalOnMissingBean
:當(dāng)容器里沒(méi)有指定 Bean 的情況下
@ConditionalOnSingleCandidate
:當(dāng)指定 Bean 在容器中只有一個(gè),或者雖然有多個(gè)但是指定首選 Bean
@ConditionalOnClass
:當(dāng)類路徑下有指定類的條件下
@ConditionalOnMissingClass
:當(dāng)類路徑下沒(méi)有指定類的條件下
@ConditionalOnProperty
:指定的屬性是否有指定的值
@ConditionalOnResource
:類路徑是否有指定的值
@ConditionalOnExpression
:基于 SpEL 表達(dá)式作為判斷條件
@ConditionalOnJava
:基于 Java 版本作為判斷條件
@ConditionalOnJndi
:在 JNDI 存在的條件下差在指定的位置
@ConditionalOnNotWebApplication
:當(dāng)前項(xiàng)目不是 Web 項(xiàng)目的條件下
@ConditionalOnWebApplication
:當(dāng)前項(xiàng)目是 Web 項(xiàng) 目的條件下
光說(shuō)不練假把式,現(xiàn)在就來(lái)擼一個(gè) starter,實(shí)現(xiàn)自定義線程池
第一步,創(chuàng)建threadpool-spring-boot-starter
工程
第二步,引入 Spring Boot 相關(guān)依賴
第三步,創(chuàng)建ThreadPoolAutoConfiguration
第四步,在threadpool-spring-boot-starter
工程的 resources 包下創(chuàng)建META-INF/spring.factories
文件
最后新建工程引入threadpool-spring-boot-starter
測(cè)試通過(guò)?。?!
Spring Boot 通過(guò)@EnableAutoConfiguration
開啟自動(dòng)裝配,通過(guò) SpringFactoriesLoader 最終加載META-INF/spring.factories
中的自動(dòng)配置類實(shí)現(xiàn)自動(dòng)裝配,自動(dòng)配置類其實(shí)就是通過(guò)@Conditional
按需加載的配置類,想要其生效必須引入spring-boot-starter-xxx
包實(shí)現(xiàn)起步依賴
關(guān)于SpringBoot自動(dòng)裝配原理是什么就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。