1. 程式人生 > 其它 >【博學谷學習記錄】超強總結,用心分享|狂野架構SpringBoot自動配置

【博學谷學習記錄】超強總結,用心分享|狂野架構SpringBoot自動配置

自動配置:根據我們新增的jar包依賴,會自動將一些配置類的bean註冊進ioc容器,我們可以需要的地方使用@Autowired或者@Resource等註解來使用它。

問題:Spring Boot到底是如何進行自動配置的,都把哪些元件進行了自動配置?

Spring Boot應用的啟動入口是@SpringBootApplication註解標註類中的main()方法,

@SpringBootApplication:SpringBoot應用標註在某個類上說明這個類是SpringBoot的主配置類,SpringBoot就應該執行這個類的main()方法啟動SpringBoot應用。

@SpringBootApplication
下面,檢視@SpringBootApplication內部原始碼進行分析 ,核心程式碼具體如下

@Target({ElementType.TYPE}) //註解的適用範圍,Type表示註解可以描述在類、介面、註解或列舉中
@Retention(RetentionPolicy.RUNTIME) //表示註解的生命週期,Runtime執行時
@Documented //表示註解可以記錄在javadoc中
@Inherited //表示可以被子類繼承該註解
@SpringBootConfiguration // 標明該類為配置類
@EnableAutoConfiguration // 啟動自動配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

// 根據class來排除特定的類,使其不能加入spring容器,傳入引數value型別是class型別。
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};

// 根據classname 來排除特定的類,使其不能加入spring容器,傳入引數value型別是class的全類名字串陣列。
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};

// 指定掃描包,引數是包名的字串陣列。
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};

// 掃描特定的包,引數類似是Class型別陣列。
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};

}
從上述原始碼可以看出,@SpringBootApplication註解是一個組合註解,前面 4 個是註解的元資料資訊, 我們主要看後面 3 個註解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三個核心註解,關於這三個核心註解的相關說明具體如下

@SpringBootConfiguration
@SpringBootConfiguration:SpringBoot的配置類,標註在某個類上,表示這是一個SpringBoot的配置類。

檢視@SpringBootConfiguration註解原始碼,核心程式碼具體如下。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented

@Configuration // 配置類的作用等同於配置檔案,配置類也是容器中的一個物件
public @interface SpringBootConfiguration {
}
從上述原始碼可以看出,@SpringBootConfiguration註解內部有一個核心註解@Configuration,該註解是Spring框架提供的,表示當前類為一個配置類(XML配置檔案的註解表現形式),並可以被元件掃描器掃描。由此可見,@SpringBootConfiguration註解的作用與@Configuration註解相同,都是標識一個可以被元件掃描器掃描的配置類,只不過@SpringBootConfiguration是被Spring Boot進行了重新封裝命名而已

@EnableAutoConfiguration
package org.springframework.boot.autoconfigure;

// 自動配置包
@AutoConfigurationPackage

// Spring的底層註解@Import,給容器中匯入一個元件;
// 匯入的元件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationImportSelector.class)

// 告訴SpringBoot開啟自動配置功能,這樣自動配置才能生效。
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

// 返回不會被匯入到 Spring 容器中的類
Class<?>[] exclude() default {};

// 返回不會被匯入到 Spring 容器中的類名
String[] excludeName() default {};

}
Spring 中有很多以Enable開頭的註解,其作用就是藉助@Import來收集並註冊特定場景相關的Bean,並載入到IOC容器。

@EnableAutoConfiguration就是藉助@Import來收集所有符合自動配置條件的bean定義,並載入到IoC容器。

@AutoConfigurationPackage
package org.springframework.boot.autoconfigure;

@Import(AutoConfigurationPackages.Registrar.class) // 匯入Registrar中註冊的元件
public @interface AutoConfigurationPackage {

}
@AutoConfigurationPackage:自動配置包,它也是一個組合註解,其中最重要的註解是@Import(AutoConfigurationPackages.Registrar.class),它是Spring框架的底層註解,它的作用就是給容器中匯入某個元件類,例如@Import(AutoConfigurationPackages.Registrar.class),它就是將Registrar這個元件類匯入到容器中,可檢視Registrar類中registerBeanDefinitions方法:

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 將註解標註的元資訊傳入,獲取到相應的包名
register(registry, new PackageImport(metadata).getPackageName());
}
我們對new PackageImport(metadata).getPackageName()進行檢索,看看其結果是什麼?

可以發現,結果是:com.itheima 是包名

再看register方法

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 這裡引數 packageNames 預設情況下就是一個字串,是使用了註解
    // @SpringBootApplication 的 Spring Boot 應用程式入口類所在的包
    
    if (registry.containsBeanDefinition(BEAN)) {
        // 如果該bean已經註冊,則將要註冊包名稱新增進去
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition
                .getConstructorArgumentValues();
        constructorArguments.addIndexedArgumentValue(0,
                addBasePackages(constructorArguments, packageNames));
    }
    else {
        //如果該bean尚未註冊,則註冊該bean,引數中提供的包名稱會被設定到bean定義中去
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
                packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}

AutoConfigurationPackages.Registrar這個類就幹一個事,註冊一個Bean,這個Bean就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages,它有一個引數,這個引數是使用了@AutoConfigurationPackage這個註解的類所在的包路徑,儲存自動配置類以供之後的使用,比如給JPA entity掃描器用來掃描開發人員通過註解@Entity定義的entity類。

@Import(AutoConfigurationImportSelector.class)
@Import({AutoConfigurationImportSelector.class}):將AutoConfigurationImportSelector這個類匯入到Spring容器中,AutoConfigurationImportSelector可以幫助Springboot應用將所有符合條件的@Configuration配置都載入到當前SpringBoot建立並使用的IOC容器(ApplicationContext)中。

可以看到AutoConfigurationImportSelector重點是實現了DeferredImportSelector介面和各種Aware介面,然後DeferredImportSelector介面又繼承了ImportSelector介面。

其不光實現了ImportSelector介面,還實現了很多其它的Aware介面,分別表示在某個時機會被回撥。

確定自動配置實現邏輯的入口方法:

跟自動配置邏輯相關的入口方法在DeferredImportSelectorGrouping類的getImports方法處,因此我們就從DeferredImportSelectorGrouping類的getImports方法來開始分析SpringBoot的自動配置原始碼好了。

先看一下getImports方法程式碼:

// ConfigurationClassParser.java

public Iterable<Group.Entry> getImports() {
// 遍歷DeferredImportSelectorHolder物件集合deferredImports,deferredImports集合裝了各種ImportSelector,當然這裡裝的是AutoConfigurationImportSelector
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
// 【1】,利用AutoConfigurationGroup的process方法來處理自動配置的相關邏輯,決定匯入哪些配置類(這個是我們分析的重點,自動配置的邏輯全在這了)
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
// 【2】,經過上面的處理後,然後再進行選擇匯入哪些配置類
return this.group.selectImports();
}
標【1】處的的程式碼是我們分析的重中之重,自動配置的相關的絕大部分邏輯全在這裡了。那麼this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());主要做的事情就是在this.group即AutoConfigurationGroup物件的process方法中,傳入的AutoConfigurationImportSelector物件來選擇一些符合條件的自動配置類,過濾掉一些不符合條件的自動配置類,就是這麼個事情。

注:
AutoConfigurationGroup:是AutoConfigurationImportSelector的內部類,主要用來處理自動配置相關的邏輯,擁有process和selectImports方法,然後擁有entries和autoConfigurationEntries集合屬性,這兩個集合分別儲存被處理後的符合條件的自動配置類,我們知道這些就足夠了;
AutoConfigurationImportSelector:承擔自動配置的絕大部分邏輯,負責選擇一些符合條件的自動配置類;
metadata:標註在SpringBoot啟動類上的@SpringBootApplication註解元資料
標【2】的this.group.selectImports的方法主要是針對前面的process方法處理後的自動配置類再進一步有選擇的選擇匯入
再進入到AutoConfigurationImportSelector$AutoConfigurationGroup的process方法:

通過圖中我們可以看到,跟自動配置邏輯相關的入口方法在process方法中

分析自動配置的主要邏輯

// AutoConfigurationImportSelector$AutoConfigurationGroup.java

// 這裡用來處理自動配置類,比如過濾掉不符合匹配條件的自動配置類
public void process(AnnotationMetadata annotationMetadata,
DeferredImportSelector deferredImportSelector) {
Assert.state(
deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 【1】,呼叫getAutoConfigurationEntry方法得到自動配置類放入autoConfigurationEntry物件中
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
annotationMetadata);
// 【2】,又將封裝了自動配置類的autoConfigurationEntry物件裝進autoConfigurationEntries集合
this.autoConfigurationEntries.add(autoConfigurationEntry);
// 【3】,遍歷剛獲取的自動配置類
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
// 這裡符合條件的自動配置類作為key,annotationMetadata作為值放進entries集合
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
上面程式碼中我們再來看標【1】的方法getAutoConfigurationEntry,這個方法主要是用來獲取自動配置類有關,承擔了自動配置的主要邏輯。直接上程式碼:

// AutoConfigurationImportSelector.java

// 獲取符合條件的自動配置類,避免載入不必要的自動配置類從而造成記憶體浪費
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// 獲取是否有配置spring.boot.enableautoconfiguration屬性,預設返回true
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 獲得@Congiguration標註的Configuration類即被審視introspectedClass的註解資料,
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 將會獲取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的註解資料
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 【1】得到spring.factories檔案配置的所有自動配置類
List configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 利用LinkedHashSet移除重複的配置類
configurations = removeDuplicates(configurations);
// 得到要排除的自動配置類,比如註解屬性exclude的配置類
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 將會獲取到exclude = FreeMarkerAutoConfiguration.class的註解資料
Set exclusions = getExclusions(annotationMetadata, attributes);
// 檢查要被排除的配置類,因為有些不是自動配置類,故要丟擲異常
checkExcludedClasses(configurations, exclusions);
// 【2】將要排除的配置類移除
configurations.removeAll(exclusions);
// 【3】因為從spring.factories檔案獲取的自動配置類太多,如果有些不必要的自動配置類都載入進記憶體,會造成記憶體浪費,因此這裡需要進行過濾
// 注意這裡會呼叫AutoConfigurationImportFilter的match方法來判斷是否符合@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,後面會重點分析一下
configurations = filter(configurations, autoConfigurationMetadata);
// 【4】獲取了符合條件的自動配置類後,此時觸發AutoConfigurationImportEvent事件,
// 目的是告訴ConditionEvaluationReport條件評估報告器物件來記錄符合條件的自動配置類
// 該事件什麼時候會被觸發?--> 在重新整理容器時呼叫invokeBeanFactoryPostProcessors後置處理器時觸發
fireAutoConfigurationImportEvents(configurations, exclusions);
// 【5】將符合條件和要排除的自動配置類封裝進AutoConfigurationEntry物件,並返回
return new AutoConfigurationEntry(configurations, exclusions);
}
深入 getCandidateConfigurations 方法
這個方法中有一個重要方法loadFactoryNames,這個方法是讓SpringFactoryLoader去載入一些元件的名字。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
 // 這個方法需要傳入兩個引數getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader()
 // getSpringFactoriesLoaderFactoryClass()這個方法返回的是EnableAutoConfiguration.class
 // getBeanClassLoader()這個方法返回的是beanClassLoader(類載入器)
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
            getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
            + "are using a custom packaging, make sure that file is correct.");
    return configurations;

繼續點開loadFactory方法

public static List loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {

    //獲取出入的鍵
    String factoryClassName = factoryClass.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
          
            //如果類載入器不為null,則載入類路徑下spring.factories檔案,將其中設定的配置類的全路徑資訊封裝 為Enumeration類物件
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            //迴圈Enumeration類物件,根據相應的節點資訊生成Properties物件,通過傳入的鍵獲取值,在將值切割為一個個小的字串轉化為Array,方法result集合中
            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryClassName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

                    for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryName = var9[var11];
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }

            cache.put(classLoader, result);
            return result;
   }

}
}
從程式碼中我們可以知道,在這個方法中會遍歷整個ClassLoader中所有jar包下的spring.factories檔案。

spring.factories裡面儲存著springboot的預設提供的自動配置類。

META-INF/spring.factories

AutoConfigurationEntry方法主要做的事情就是獲取符合條件的自動配置類,避免載入不必要的自動配置類從而造成記憶體浪費。我們下面總結下AutoConfigurationEntry方法主要做的事情:

【1】從spring.factories配置檔案中載入EnableAutoConfiguration自動配置類),獲取的自動配置類如圖所示。

【2】若@EnableAutoConfiguration等註解標有要exclude的自動配置類,那麼再將這個自動配置類排除掉;

【3】排除掉要exclude的自動配置類後,然後再呼叫filter方法進行進一步的過濾,再次排除一些不符合條件的自動配置類;

【4】經過重重過濾後,此時再觸發AutoConfigurationImportEvent事件,告訴ConditionEvaluationReport條件評估報告器物件來記錄符合條件的自動配置類;

【5】 最後再將符合條件的自動配置類返回。

總結了AutoConfigurationEntry方法主要的邏輯後,我們再來細看一下AutoConfigurationImportSelector的filter方法:

// AutoConfigurationImportSelector.java

private List filter(List configurations,
AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
// 將從spring.factories中獲取的自動配置類轉出字串陣列
String[] candidates = StringUtils.toStringArray(configurations);
// 定義skip陣列,是否需要跳過。注意skip陣列與candidates陣列順序一一對應
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
// getAutoConfigurationImportFilters方法:拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition
// 然後遍歷這三個條件類去過濾從spring.factories載入的大量配置類
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
// 呼叫各種aware方法,將beanClassLoader,beanFactory等注入到filter物件中,
// 這裡的filter物件即OnBeanCondition,OnClassCondition或OnWebApplicationCondition
invokeAwareMethods(filter);
// 判斷各種filter來判斷每個candidate(這裡實質要通過candidate(自動配置類)拿到其標註的
// @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication裡面的註解值)是否匹配,
// 注意candidates陣列與match陣列一一對應
/**********************【主線,重點關注】********************************/
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
// 遍歷match陣列,注意match順序跟candidates的自動配置類一一對應
for (int i = 0; i < match.length; i++) {
// 若有不匹配的話
if (!match[i]) {
// 不匹配的將記錄在skip陣列,標誌skip[i]為true,也與candidates陣列一一對應
skip[i] = true;
// 因為不匹配,將相應的自動配置類置空
candidates[i] = null;
// 標註skipped為true
skipped = true;
}
}
}
// 這裡表示若所有自動配置類經過OnBeanCondition,OnClassCondition和OnWebApplicationCondition過濾後,全部都匹配的話,則全部原樣返回
if (!skipped) {
return configurations;
}
// 建立result集合來裝匹配的自動配置類
List result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
// 若skip[i]為false,則說明是符合條件的自動配置類,此時新增到result集合中
if (!skip[i]) {
result.add(candidates[i]);
}
}
// 列印日誌
if (logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
+ " ms");
}
// 最後返回符合條件的自動配置類
return new ArrayList<>(result);
}
AutoConfigurationImportSelector的filter方法主要做的事情就是呼叫AutoConfigurationImportFilter介面的match方法來判斷每一個自動配置類上的條件註解(若有的話)@ConditionalOnClass,@ConditionalOnBean或@ConditionalOnWebApplication是否滿足條件,若滿足,則返回true,說明匹配,若不滿足,則返回false說明不匹配。

我們現在知道AutoConfigurationImportSelector的filter方法主要做了什麼事情就行了,現在先不用研究的過深

關於條件註解的講解
@Conditional是Spring4新提供的註解,它的作用是按照一定的條件進行判斷,滿足條件給容器註冊bean。

@ConditionalOnBean:僅僅在當前上下文中存在某個物件時,才會例項化一個Bean。
@ConditionalOnClass:某個class位於類路徑上,才會例項化一個Bean。
@ConditionalOnExpression:當表示式為true的時候,才會例項化一個Bean。基於SpEL表示式的條件判斷。
@ConditionalOnMissingBean:僅僅在當前上下文中不存在某個物件時,才會例項化一個Bean。
@ConditionalOnMissingClass:某個class類路徑上不存在的時候,才會例項化一個Bean。
@ConditionalOnNotWebApplication:不是web應用,才會例項化一個Bean。
@ConditionalOnWebApplication:當專案是一個Web專案時進行例項化。
@ConditionalOnNotWebApplication:當專案不是一個Web專案時進行例項化。
@ConditionalOnProperty:當指定的屬性有指定的值時進行例項化。
@ConditionalOnJava:當JVM版本為指定的版本範圍時觸發例項化。
@ConditionalOnResource:當類路徑下有指定的資源時觸發例項化。
@ConditionalOnJndi:在JNDI存在的條件下觸發例項化。
@ConditionalOnSingleCandidate:當指定的Bean在容器中只有一個,或者有多個但是指定了首選的Bean時觸發例項化。

有選擇的匯入自動配置類

this.group.selectImports方法是如何進一步有選擇的匯入自動配置類的。直接看程式碼:

// AutoConfigurationImportSelector$AutoConfigurationGroup.java

public Iterable selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
// 這裡得到所有要排除的自動配置類的set集合
Set allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions)
.flatMap(Collection::stream).collect(Collectors.toSet());
// 這裡得到經過濾後所有符合條件的自動配置類的set集合
Set processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations)
.flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
// 移除掉要排除的自動配置類
processedConfigurations.removeAll(allExclusions);
// 對標註有@Order註解的自動配置類進行排序,
return sortAutoConfigurations(processedConfigurations,
getAutoConfigurationMetadata())
.stream()
.map((importClassName) -> new Entry(
this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
可以看到,selectImports方法主要是針對經過排除掉exclude的和被AutoConfigurationImportFilter介面過濾後的滿足條件的自動配置類再進一步排除exclude的自動配置類,然後再排序

最後,我們再總結下SpringBoot自動配置的原理,主要做了以下事情:

從spring.factories配置檔案中載入自動配置類;
載入的自動配置類中排除掉@EnableAutoConfiguration註解的exclude屬性指定的自動配置類;
然後再用AutoConfigurationImportFilter介面去過濾自動配置類是否符合其標註註解(若有標註的話)@ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication的條件,若都符合的話則返回匹配結果;
然後觸發AutoConfigurationImportEvent事件,告訴ConditionEvaluationReport條件評估報告器物件來分別記錄符合條件和exclude的自動配置類。
最後spring再將最後篩選後的自動配置類匯入IOC容器中

以HttpEncodingAutoConfiguration(Http編碼自動配置)為例解釋自動配置原理
// 表示這是一個配置類,和以前編寫的配置檔案一樣,也可以給容器中新增元件
@Configuration

// 啟動指定類的ConfigurationProperties功能;將配置檔案中對應的值和HttpEncodingProperties繫結起來;
@EnableConfigurationProperties({HttpEncodingProperties.class})

// Spring底層@Conditional註解,根據不同的條件,如果滿足指定的條件,整個配置類裡面的配置就會生效。
// 判斷當前應用是否是web應用,如果是,當前配置類生效。並把HttpEncodingProperties加入到 ioc 容器中
@ConditionalOnWebApplication

// 判斷當前專案有沒有這個CharacterEncodingFilter : SpringMVC中進行亂碼解決的過濾器
@ConditionalOnClass({CharacterEncodingFilter.class})

// 判斷配置檔案中是否存在某個配置 spring.http.encoding.enabled 如果不存在,判斷也是成立的
// matchIfMissing = true 表示即使我們配置檔案中不配置spring.http.encoding.enabled=true,也是預設生效的
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {

// 它已經和SpringBoot配置檔案中的值進行映射了
private final HttpEncodingProperties properties;

// 只有一個有參構造器的情況下,引數的值就會從容器中拿
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
    this.properties = properties;
}

@Bean   //給容器中新增一個元件,這個元件中的某些值需要從properties中獲取
@ConditionalOnMissingBean({CharacterEncodingFilter.class})  //判斷容器中沒有這個元件
public CharacterEncodingFilter characterEncodingFilter() {
    CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
    filter.setEncoding(this.properties.getCharset().name());
    filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
    filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
    return filter;
}

根據當前不同的條件判斷,決定這個配置類是否生效。

一旦這個配置類生效,這個配置類就會給容器中新增各種元件;這些元件的屬性是從對應的properties類中獲取的,這些類裡面的每一個屬性又是和配置檔案繫結的。

我們能配置的屬性都是來源於這個功能的properties類

spring.http.encoding.enabled=true
spring.http.encoding.charset=utf-8
spring.http.encoding.force=true
所有在配置檔案中能配置的屬性都是在 xxxProperties 類中封裝著,配置檔案能配置什麼就可以參照某個功能對應的這個屬性類。

// 從配置檔案中獲取指定的值和bean的屬性進行繫結
@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
精髓
SpringBoot 啟動會載入大量的自動配置類
我們看我們需要實現的功能有沒有SpringBoot預設寫好的自動配置類
我們再來看這個自動配置類中到底配置了哪些元件;(只要我們有我們要用的元件,我們就不需要再來配置了)
給容器中自動配置類新增元件的時候,會從properties類中獲取某些屬性,我們就可以在配置檔案中指定這些屬性的值。
xxxAutoConfiguration:自動配置類,用於給容器中新增元件從而代替之前我們手動完成大量繁瑣的配置。

xxxProperties : 封裝了對應自動配置類的預設屬性值,如果我們需要自定義屬性值,只需要根據xxxProperties尋找相關屬性在配置檔案設值即可。

@ComponentScan註解
@ComponentScan使用
主要是從定義的掃描路徑中,找出標識了需要裝配的類自動裝配到spring 的bean容器中。

常用屬性如下:

basePackages、value:指定掃描路徑,如果為空則以@ComponentScan註解的類所在的包為基本的掃描路徑
basePackageClasses:指定具體掃描的類
includeFilters:指定滿足Filter條件的類
excludeFilters:指定排除Filter條件的類
includeFilters和excludeFilters 的FilterType可選:ANNOTATION=註解型別 預設、ASSIGNABLE_TYPE(指定固定類)、ASPECTJ(ASPECTJ型別)、REGEX(正則表示式)、CUSTOM(自定義型別),自定義的Filter需要實現TypeFilter介面

@ComponentScan的配置如下:

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
藉助excludeFilters將TypeExcludeFillter及FilterType這兩個類進行排除

當前@ComponentScan註解沒有標註basePackages及value,所以掃描路徑預設為@ComponentScan註解的類所在的包為基本的掃描路徑(也就是標註了@SpringBootApplication註解的專案啟動類所在的路徑)