【博學谷學習記錄】超強總結,用心分享|狂野架構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
attributes);
// 利用LinkedHashSet移除重複的配置類
configurations = removeDuplicates(configurations);
// 得到要排除的自動配置類,比如註解屬性exclude的配置類
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 將會獲取到exclude = FreeMarkerAutoConfiguration.class的註解資料
Set
// 檢查要被排除的配置類,因為有些不是自動配置類,故要丟擲異常
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
//獲取出入的鍵
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
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
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
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
// 這裡得到所有要排除的自動配置類的set集合
Set
.map(AutoConfigurationEntry::getExclusions)
.flatMap(Collection::stream).collect(Collectors.toSet());
// 這裡得到經過濾後所有符合條件的自動配置類的set集合
Set
.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註解的專案啟動類所在的路徑)