本文已經收錄進 Github 95k+ Star 的Java項目JavaGuide 。JavaGuide項目地址 : https://github.com/Snailclimb/JavaGuide 。
作者:Miki-byte-1024 & Snailclimb
每次問到 Spring Boot, 面試官非常喜歡問這個問題:“講述一下 SpringBoot 自動裝配原理?”。
我覺得我們可以從以下幾個方面回答:
- 什么是 SpringBoot 自動裝配?
- SpringBoot 是如何實現自動裝配的?如何實現按需加載?
- 如何實現一個 Starter?
篇幅問題,這篇文章並沒有深入,小伙伴們也可以直接使用 debug 的方式去看看 SpringBoot 自動裝配部分的源代碼。
前言
使用過 Spring 的小伙伴,一定有被 XML 配置統治的恐懼。即使 Spring 后面引入了基於注解的配置,我們在開啟某些 Spring 特性或者引入第三方依賴的時候,還是需要用 XML 或 Java 進行顯式配置。
舉個例子。沒有 Spring Boot 的時候,我們寫一個 RestFul Web 服務,還首先需要進行如下配置。
@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 項目,我們只需要添加相關依賴,無需配置,通過啟動下面的 main
方法即可。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
並且,我們通過 Spring Boot 的全局配置文件 application.properties
或application.yml
即可對項目進行設置比如更換端口號,配置 JPA 屬性等等。
為什么 Spring Boot 使用起來這么酸爽呢? 這得益於其自動裝配。自動裝配可以說是 Spring Boot 的核心,那究竟什么是自動裝配呢?
什么是 SpringBoot 自動裝配?
我們現在提到自動裝配的時候,一般會和 Spring Boot 聯系在一起。但是,實際上 Spring Framework 早就實現了這個功能。Spring Boot 只是在其基礎上,通過 SPI 的方式,做了進一步優化。
SpringBoot 定義了一套接口規范,這套規范規定:SpringBoot 在啟動時會掃描外部引用 jar 包中的
META-INF/spring.factories
文件,將文件中配置的類型信息加載到 Spring 容器(此處涉及到 JVM 類加載機制與 Spring 的容器知識),並執行類中定義的各種操作。對於外部 jar 來說,只需要按照 SpringBoot 定義的標准,就能將自己的功能裝置進 SpringBoot。
沒有 Spring Boot 的情況下,如果我們需要引入第三方依賴,需要手動配置,非常麻煩。但是,Spring Boot 中,我們直接引入一個 starter 即可。比如你想要在項目中使用 redis 的話,直接在項目中引入對應的 starter 即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
引入 starter 之后,我們通過少量注解和一些簡單的配置就能使用第三方組件提供的功能了。
在我看來,自動裝配可以簡單理解為:通過注解或者一些簡單的配置就能在 Spring Boot 的幫助下實現某塊功能。
SpringBoot 是如何實現自動裝配的?
我們先看一下 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 //實際上它也是一個配置類
public @interface SpringBootConfiguration {
}
大概可以把 @SpringBootApplication
看作是 @Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。根據 SpringBoot 官網,這三個注解的作用分別是:
-
@EnableAutoConfiguration
:啟用 SpringBoot 的自動配置機制 -
@Configuration
:允許在上下文中注冊額外的 bean 或導入其他配置類 -
@ComponentScan
: 掃描被@Component
(@Service
,@Controller
)注解的 bean,注解默認會掃描啟動類所在的包下所有的類 ,可以自定義不掃描某些 bean。如下圖所示,容器中將排除TypeExcludeFilter
和AutoConfigurationExcludeFilter
。
@EnableAutoConfiguration
是實現自動裝配的重要注解,我們以這個注解入手。
@EnableAutoConfiguration:實現自動裝配的核心注解
EnableAutoConfiguration
只是一個簡單地注解,自動裝配核心功能的實現實際是通過 AutoConfigurationImportSelector
類。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //作用:將main包下的所欲組件注冊到容器中
@Import({AutoConfigurationImportSelector.class}) //加載自動裝配類 xxxAutoconfiguration
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
我們現在重點分析下AutoConfigurationImportSelector
類到底做了什么?
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
類實現了 ImportSelector
接口,也就實現了這個接口中的 selectImports
方法,該方法主要用於獲取所有符合條件的類的全限定類名,這些類需要被加載到 IoC 容器中。
private static final String[] NO_IMPORTS = new String[0];
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// <1>.判斷自動裝配開關是否打開
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());
}
}
這里我們需要重點關注一下getAutoConfigurationEntry()
方法,這個方法主要負責加載自動配置類的。
該方法調用鏈如下:
現在我們結合getAutoConfigurationEntry()
的源碼來詳細分析一下:
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 步:
判斷自動裝配開關是否打開。默認spring.boot.enableautoconfiguration=true
,可在 application.properties
或 application.yml
中設置
第 2 步 :
用於獲取EnableAutoConfiguration
注解中的 exclude
和 excludeName
。
第 3 步
獲取需要自動裝配的所有配置類,讀取META-INF/spring.factories
spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
從下圖可以看到這個文件的配置內容都被我們讀取到了。XXXAutoConfiguration
的作用就是按需加載組件。
不光是這個依賴下的META-INF/spring.factories
被讀取到,所有 Spring Boot Starter 下的META-INF/spring.factories
都會被讀取到。
所以,你可以清楚滴看到, druid 數據庫連接池的 Spring Boot Starter 就創建了META-INF/spring.factories
文件。
如果,我們自己要創建一個 Spring Boot Starter,這一步是必不可少的。
第 4 步 :
到這里可能面試官會問你:“spring.factories
中這么多配置,每次啟動都要全部加載么?”。
很明顯,這是不現實的。我們 debug 到后面你會發現,configurations
的值變小了。
因為,這一步有經歷了一遍篩選,@ConditionalOnXXX
中的所有條件都滿足,該類才會生效。
@Configuration
// 檢查相關的類:RabbitTemplate 和 Channel是否存在
// 存在才會加載
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}
有興趣的童鞋可以詳細了解下 Spring Boot 提供的條件注解
@ConditionalOnBean
:當容器里有指定 Bean 的條件下@ConditionalOnMissingBean
:當容器里沒有指定 Bean 的情況下@ConditionalOnSingleCandidate
:當指定 Bean 在容器中只有一個,或者雖然有多個但是指定首選 Bean@ConditionalOnClass
:當類路徑下有指定類的條件下@ConditionalOnMissingClass
:當類路徑下沒有指定類的條件下@ConditionalOnProperty
:指定的屬性是否有指定的值@ConditionalOnResource
:類路徑是否有指定的值@ConditionalOnExpression
:基於 SpEL 表達式作為判斷條件@ConditionalOnJava
:基於 Java 版本作為判斷條件@ConditionalOnJndi
:在 JNDI 存在的條件下差在指定的位置@ConditionalOnNotWebApplication
:當前項目不是 Web 項目的條件下@ConditionalOnWebApplication
:當前項目是 Web 項 目的條件下
如何實現一個 Starter
光說不練假把式,現在就來擼一個 starter,實現自定義線程池
第一步,創建threadpool-spring-boot-starter
工程
第二步,引入 Spring Boot 相關依賴
第三步,創建ThreadPoolAutoConfiguration
第四步,在threadpool-spring-boot-starter
工程的 resources 包下創建META-INF/spring.factories
文件
最后新建工程引入threadpool-spring-boot-starter
測試通過!!!
總結
Spring Boot 通過@EnableAutoConfiguration
開啟自動裝配,通過 SpringFactoriesLoader 最終加載META-INF/spring.factories
中的自動配置類實現自動裝配,自動配置類其實就是通過@Conditional
按需加載的配置類,想要其生效必須引入spring-boot-starter-xxx
包實現起步依賴