👉 這是一個或許對你有用的社群
《專案實戰(影片)》:從書中學,往事上“練” 《網際網路高頻面試題》:面朝簡歷學習,春暖花開 《架構 x 系統設計》:摧枯拉朽,掌控面試高頻場景題 《精進 Java 學習指南》:系統學習,網際網路主流技術棧 《必讀 Java 原始碼專欄》:知其然,知其所以然

👉這是一個或許對你有用的開源專案國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERP、CRM、AI 大模型等等功能:
Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud 影片教程:https://doc.iocoder.cn 【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本

一 @SpringBootApplication
@SpringBootApplication
說起,這是整個 Spring Boot 宇宙的起點,我們先來看下這個註解:@Target
(ElementType.TYPE)
@Retention
(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
(excludeFilters = {
@Filter
(type = FilterType.CUSTOM, classes = TypeExcludeFilter
.
class
),
@
Filter
(
type
= FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter
.
class
) })
public
@
interfaceSpringBootApplication
{
}
@SpringBootApplication
註解組合了多個常見註解的功能,其中:-
前四個是元註解,這裡我們不做討論。 -
第五個 @SpringBootConfiguration
是一個支援配置類的註解,這裡我們也不做討論。 -
第六個 @EnableAutoConfiguration
這個註解就表示開啟自動化配置,這是我們今天要聊得重點。 -
第七個 @ComponentScan
是一個包掃描註解,為什麼Spring Boot
專案中的Bean
只要放對位置就會被自動掃描到,和這個註解有關。
Spring Boot
提供的註解一共就兩個,分別是 @SpringBootConfiguration
和 @EnableAutoConfiguration
兩個,其他註解在 Spring Boot
出現之前就已經存在多年了。基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
專案地址:https://github.com/YunaiV/ruoyi-vue-pro 影片教程:https://doc.iocoder.cn/video/
二 @EnableAutoConfiguration
@EnableAutoConfiguration
是如何實現自動化配置的。@Target
(ElementType.TYPE)
@Retention
(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import
(AutoConfigurationImportSelector
.
class
)
public
@
interfaceEnableAutoConfiguration
{
}
-
@AutoConfigurationPackage
:這個表示自動掃描各種第三方的註解 -
@Import
則是在匯入AutoConfigurationImportSelector
配置類,這個配置類裡邊就是去載入各種自動化配置類的。
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
專案地址:https://github.com/YunaiV/yudao-cloud 影片教程:https://doc.iocoder.cn/video/
三 AutoConfigurationImportSelector
AutoConfigurationImportSelector
類中的方法比較多,入口的地方則是 process 方法,所以我們這裡就從 process 方法開始看起:@Override
publicvoidprocess(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector)
{
Assert.state(deferredImportSelector
instanceof
AutoConfigurationImportSelector,
() -> String.format(
"Only %s implementations are supported, got %s"
,
AutoConfigurationImportSelector
.
class
.
getSimpleName
(),
deferredImportSelector
.
getClass
().
getName
()))
;
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this
.autoConfigurationEntries.add(autoConfigurationEntry);
for
(String importClassName : autoConfigurationEntry.getConfigurations()) {
this
.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
進行載入的。getAutoConfigurationEntry
方法實際上就是當前類提供的方法,我們來看下該方法:protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)
{
if
(!isEnabled(annotationMetadata)) {
return
EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
returnnew
AutoConfigurationEntry(configurations, exclusions);
}
3.1 isEnabled
spring.boot.enableautoconfiguration=false
來關閉所有的自動化配置。protectedbooleanisEnabled(AnnotationMetadata metadata)
{
if
(getClass() == AutoConfigurationImportSelector
.class)
{
return
getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean
.class, true)
;
}
returntrue
;
}
3.2 getCandidateConfigurations
getCandidateConfigurations
方法去獲取所有候選的自動化配置類,這些候選的自動化配置類主要來自兩個地方:-
在之前的自定義 starter
中松哥和大家聊過,我們需要在claspath\:META-INF/spring.factories
中定義出來所有的自動化配置類,這是來源一。 -
Spring Boot
自帶的自動化配置類,這個在之前的 vhr 影片中也和小夥伴們多次講過,Spring Boot
自帶的自動化配置類位於spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports
檔案中。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
{
List<String> configurations =
new
ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration
.class, getBeanClassLoader()).forEach(configurations::add)
;
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+
"are using a custom packaging, make sure that file is correct."
);
return
configurations;
}
configurations
物件中,該物件有兩個獲取的地方:-
呼叫 SpringFactoriesLoader.loadFactoryNames
方法獲取,這個方法細節我就不帶大家看了,比較簡單,本質上就是去載入META-INF/spring.factories
檔案,這個檔案中定義了大量的自動化配置類的全路徑。 -
呼叫 ImportCandidates.load
方法去載入,這個就是載入spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports
檔案中的自動化配置類。
3.3 removeDuplicates
removeDuplicates
方法表示移除候選自動化配置類中重複的類,移除的思路也很有意思,就用一個 LinkedHashSet
中轉一下就行了,原始碼如下:protectedfinal
<T>
List<T> removeDuplicates(List<T> list)
{
returnnew
ArrayList<>(
new
LinkedHashSet<>(list));
}
3.4 getExclusions
getExclusions
方法表示需要獲取到所有被排除的自動化配置類,這些被排除的自動化配置類可以從三個地方獲取:-
當前註解的 exclude
屬性。 -
當前註解的 excludeName
屬性。 -
application.properties
配置檔案中的spring.autoconfigure.exclude
屬性。
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes)
{
Set<String> excluded =
new
LinkedHashSet<>();
excluded.addAll(asList(attributes,
"exclude"
));
excluded.addAll(asList(attributes,
"excludeName"
));
excluded.addAll(getExcludeAutoConfigurationsProperty());
return
excluded;
}
3.5 checkExcludedClasses
Spring Boot
中的自動化配置類可以自定義,並不需要統一實現某一個介面或者統一繼承某一個類,所以在寫排除類的時候,如果寫錯了編譯是校驗不出來的,像下面這種:@SpringBootApplication
(exclude = HelloController
.
class
)
publicclassApp
{
publicstaticvoidmain(String[] args)
{
SpringApplication.run(App
.class, args)
;
}
}
HelloController
並不是一個自動化配置類,所以這樣寫專案啟動的時候就會報錯,如下:
checkExcludedClasses
方法,我們來看下該方法:privatevoidcheckExcludedClasses(List<String> configurations, Set<String> exclusions)
{
List<String> invalidExcludes =
new
ArrayList<>(exclusions.size());
for
(String exclusion : exclusions) {
if
(ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
invalidExcludes.add(exclusion);
}
}
if
(!invalidExcludes.isEmpty()) {
handleInvalidExcludes(invalidExcludes);
}
}
protectedvoidhandleInvalidExcludes(List<String> invalidExcludes)
{
StringBuilder message =
new
StringBuilder();
for
(String exclude : invalidExcludes) {
message.append(
"\t- "
).append(exclude).append(String.format(
"%n"
));
}
thrownew
IllegalStateException(String.format(
"The following classes could not be excluded because they are not auto-configuration classes:%n%s"
,
message));
}
checkExcludedClasses
方法中,會首先找到所有位於當前類路徑下但是卻不包含在 configurations
中的所有被排除的自動化配置類,由於 configurations
中的就是所有的自動化配置類了,所以這些不存在於 configurations
中的類都是有問題的,都不是自動化配置類,將這些有問題的類收集起來,存入到 invalidExcludes
變數中,然後再進行額外的處理。handleInvalidExcludes
方法中丟擲異常,前面截圖中的異常就是來自這裡。3.6 removeAll
configurations
中移除掉那些被排除的自動化配置類。configurations
本身就是 List
集合,exclusions
則是一個 Set
集合,所以這裡直接移除即可。3.7 filter
getConfigurationClassFilter().filter(configurations);
來完成的。spring-boot-autoconfigure-3.0.6.jar!/META-INF/spring-autoconfigure-metadata.properties
檔案之中,我隨便舉一個該檔案中的配置:-
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit
表示 RabbitAnnotationDrivenConfiguration 類要生效有一個必備條件就是當前專案類路徑下要存在org.springframework.amqp.rabbit.annotation.EnableRabbit
。
@Configuration
(proxyBeanMethods =
false
)
@ConditionalOnClass
(EnableRabbit
.
class
)
classRabbitAnnotationDrivenConfiguration
{
}
private ConfigurationClassFilter getConfigurationClassFilter()
{
if
(
this
.configurationClassFilter ==
null
) {
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
for
(AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter);
}
this
.configurationClassFilter =
new
ConfigurationClassFilter(
this
.beanClassLoader, filters);
}
returnthis
.configurationClassFilter;
}

-
OnClassCondition:這個就是條件註解 @ConditionalOnClass
的判定條件,看名字就知道用來判斷當前 classpath 下是否存在某個類。 -
OnWebApplicationCondition:這個是條件註解 ConditionalOnWebApplication
的判定條件,用來判斷當前系統環境是否是一個 Web 環境。 -
OnBeanCondition:這個是條件註解 @ConditionalOnBean
的判定條件,就是判斷當前系統下是否存在某個 Bean。
List<String> filter(List<String> configurations)
{
long
startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean
skipped =
false
;
for
(AutoConfigurationImportFilter filter :
this
.filters) {
boolean
[] match = filter.match(candidates,
this
.autoConfigurationMetadata);
for
(
int
i =
0
; i < match.length; i++) {
if
(!match[i]) {
candidates[i] =
null
;
skipped =
true
;
}
}
}
if
(!skipped) {
return
configurations;
}
List<String> result =
new
ArrayList<>(candidates.length);
for
(String candidate : candidates) {
if
(candidate !=
null
) {
result.add(candidate);
}
}
return
result;
}





