配置文件加載順序
SpringBoot也可以從以下位置加載配置; 優先級從高到低;高優先級的配置覆蓋低優先級的配置,所有的配置會形成互補配置
-
命令行參數
所有的配置都可以在命令行上進行指定
java -jar xxx.jar --server.port=8087 --server.context-path=/abcCopy to clipboardErrorCopied
多個配置用空格分開; --配置項=值
-
來自java:comp/env的JNDI屬性 ⤴️
-
Java系統屬性(System.getProperties()) ⤴️
-
操作系統環境變量 ⤴️
-
RandomValuePropertySource配置的random.*屬性值 ⤴️
由jar包外向jar包內進行尋找;
優先加載帶profile
-
jar包外部的
application-{profile}.properties
或application.yml
(帶spring.profile)配置文件 ⤴️ -
jar包內部的
application-{profile}.properties
或application.yml
(帶spring.profile)配置文件 ⤴️
再來加載不帶profile
-
jar包外部的
application.properties
或application.yml
(不帶spring.profile)配置文件 ⤴️ -
jar包內部的
application.properties
或application.yml
(不帶spring.profile)配置文件 ⤴️ -
@Configuration注解類上的@PropertySource ⤴️
-
通過SpringApplication.setDefaultProperties指定的默認屬性 ⤴️
所有支持的配置加載來源:
自動配置的執行流程
- @SpringBootApplication
1. @SpringBootApplication // 由啟動類的@SpringBootApplication開啟自動配置
2. @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //標至該類是一個配置類,與@Configuration作用一致
@EnableAutoConfiguration //啟動
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
從Spring3.0,@Configuration用於定義配置類,可替換xml配置文件,被注解的類內部包含有一個或多個被@Bean注解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。
注意:@Configuration注解的配置類有如下要求:
- @Configuration不可以是final類型;
- @Configuration不可以是匿名類;
- 嵌套的configuration必須是靜態類。
一、用@Configuration加載spring
1.1、@Configuration配置spring並啟動spring容器
1.2、@Configuration啟動容器+@Bean注冊Bean
1.3、@Configuration啟動容器+@Component注冊Bean
1.4、使用 AnnotationConfigApplicationContext 注冊 AppContext 類的兩種方法1.5、配置Web應用程序(web.xml中配置AnnotationConfigApplicationContext)
二、組合多個配置類
2.1、在@configuration中引入spring的xml配置文件
2.2、在@configuration中引入其它注解配置
2.3、@configuration嵌套(嵌套的Configuration必須是靜態類)
三、@EnableXXX注解
四、@Profile邏輯組配置
五、使用外部變量
- EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //當SpringBoot應用啟動時默認會將啟動類所在的package作為自動配置的package。
@Import(AutoConfigurationImportSelector.class) //@EnableAutoConfiguration注解是Spring Boot中配置自動裝載的總開關。
public @interface EnableAutoConfiguration {
}
boot.autoconfigure.EnableAutoConfiguration注解
-> @Import了一個AutoConfigurationImportSelector實例
-> AutoConfigurationImportSelector類(implement ImportSelector),實現了selectImports() 方法,用來篩選被@Import的Configuration類(減去exclude等)
- AutoConfigurationImportSelector.class
public class AutoConfigurationImportSelector implementsDeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//......
@Override
publicString[] selectImports(AnnotationMetadata annotationMetadata) {
// 如果AutoConfiguration沒開,返回{}
if(!isEnabled(annotationMetadata)) {
returnNO_IMPORTS;
}
// 將spring-autoconfigure-metadata.properties的鍵值對配置載入到PropertiesAutoConfigurationMetadata對象中並返回
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 基於各種配置計算需要import的configuration和exclusion
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
// 判斷AudoConfiguration是否開啟
protectedbooleanisEnabled(AnnotationMetadata metadata) {
if(getClass() == AutoConfigurationImportSelector.class) {
// 如果配置文件中有"spring.boot.enableautoconfiguration",返回該字段的值;否則返回true
returngetEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
returntrue;
}
protectedAutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if(!isEnabled(annotationMetadata)) {
returnEMPTY_ENTRY;
}
// 獲取注解的屬性值
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 從META-INF/spring.factories文件中獲取EnableAutoConfiguration所對應的configurations,但並不實例化,還要篩選
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重,List轉Set再轉List
configurations = removeDuplicates(configurations);
// 從注解的exclude/excludeName屬性中獲取排除項
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 對於不屬於AutoConfiguration的exclude報錯
checkExcludedClasses(configurations, exclusions);
// 從configurations去除exclusions
configurations.removeAll(exclusions);
// 所有AutoConfigurationImportFilter類實例化,並再進行一次篩選
configurations = filter(configurations, autoConfigurationMetadata);
// 實例化剩下configuration中的類,並把AutoConfigurationImportEvent綁定在所有AutoConfigurationImportListener子類實例上,當fireAutoConfigurationImportEvents事件被觸發時,打印出已經注冊到spring上下文中的@Configuration注解的類,打印出被阻止注冊到spring
fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回(configurations, exclusions)組
return newAutoConfigurationEntry(configurations, exclusions);
}
// ......
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class;
// getBeanClassLoader()這里使用的是AppClassLoader。
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;
}
- getCandidateConfigurations方法中,SpringFactoriesLoader.loadFactoryNames(),掃描所有jar包類路徑下
META-INF/spring.factories
,並對相應的key值進行篩選,這里使用的key值為org.springframework.boot.autoconfigure.EnableAutoConfiguration。 - 把掃描到的這些文件的內容包裝成properties對象,以 Properties 類型(即 key-value 形式)配置,就可以將相應的實現類注入 Spirng 容器中(key為factory類型)。從properties中獲取到EnableAutoConfiguration.class(類名)對應的值,然后把它們添加在容器中
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// loadSpringFactories方法是獲取所有的springFactories
// getOrDefault是通過key,獲取到對應的類的集合。因為value是通過逗號相隔的,可以有多個,所以是list
// getOrDefault如果存在就返回,如果不存在那么就返回給的默認值
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 三目表達式,判斷參數classLoader是否為空,如果不為空,那么直接使用傳入的classLoader獲取META-INF/spring.factories
// 如果為空,那么就使用系統的classLoader來獲取META-INF/spring.factories
// 總之健壯性比較強,
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
// 通過循環遍歷所有的META-INF/spring.factories
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 找到的每個 META-INF/spring.factories 文件都是一個 Properties 文件,將其內容加載到一個 Properties 對象然后處理其中的每個屬性
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 獲取工廠類名稱(接口或者抽象類的全限定名)
String factoryTypeName = ((String) entry.getKey()).trim();
// 將逗號分割的屬性值逐個取出,然后放到 結果result 中去
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
// 篩選出的結果集Map放入內存中,
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
- 在getConfigurationClassFilter與fireAutoConfigurationImportEvents方法中將其通過SpringFactoriesLoader 中的loadFactories反射對所有的配置進行篩選,實例化,並綁定到AutoConfigurationImportListener子類實例上
https://blog.csdn.net/qq_42154259/article/details/107600734
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
/**
* 通過classLoader從各個jar包的classpath下面的META-INF/spring.factories加載並解析其key-value值,然后創建其給定類型的工廠實現
*
* 返回的工廠通過AnnotationAwareOrderComparator進行排序過的。
* AnnotationAwareOrderComparator就是通過@Order注解上面的值進行排序的,值越高,則排的越靠后
*
* 如果需要自定義實例化策略,請使用loadFactoryNames方法獲取所有注冊工廠名稱。
*
* @param factoryType 接口或者抽象類的Class對象
* @param classLoader 用於加載的類加載器(可以是null,如果是null,則使用默認值)
* @throws IllegalArgumentException 如果無法加載任何工廠實現類,或者在實例化任何工廠時發生錯誤,則會拋出IllegalArgumentException異常
*/
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
// 首先斷言,傳入的接口或者抽象類的Class對象不能為空
Assert.notNull(factoryType, "'factoryType' must not be null");
// 將傳入的classLoader賦值給classLoaderToUse
// 判斷classLoaderToUse是否為空,如果為空,則使用默認的SpringFactoriesLoader的classLoader
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 加載所有的META-INF/spring.factories並解析,獲取其配置的factoryNames
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList<>(factoryImplementationNames.size());
// 通過反射對所有的配置進行實例化。
for (String factoryImplementationName : factoryImplementationNames) {
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
// 實例化工廠,根據
@SuppressWarnings("unchecked")
private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
try {
// 根據全限定類名通過反射獲取Class對象
Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
// 判斷獲取的Class對象是否從factoryType里面來,
// 說具體點就是判斷我們配置的spring.factories中的權限定類名所對應的類是否是對應的子類或者實現類
// 比如
// org.springframework.context.ApplicationContextInitializer=\
// org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
// org.springframework.boot.context.ContextIdApplicationContextInitializer,
// 那么他就會驗證ConfigurationWarningsApplicationContextInitializer和ContextIdApplicationContextInitializer是否是ApplicationContextInitializer的子類
// isAssignableFrom()方法與instanceof關鍵字的區別總結為以下兩個點:
// isAssignableFrom()方法是從類繼承的角度去判斷,instanceof關鍵字是從實例繼承的角度去判斷。
// isAssignableFrom()方法是判斷是否為某個類的父類,instanceof關鍵字是判斷是否某個類的子類。
// 如果不是,則會保存
if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
throw new IllegalArgumentException(
"Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
}
// 通過反射的有參構造函數進行實例化:如果直接newInstance的話,那么只能通過空參構造函數進行實例化。
// 通過這種方式可以通過不同參數的構造函數進行創建實例,但是這里並沒有傳入參數,所以調用的是默認空慘構造函數
return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
ex);
}
}
-
可見selectImports()是AutoConfigurationImportSelector的核心函數,其核心功能就是獲取spring.factories中EnableAutoConfiguration所對應的Configuration類列表,由@EnableAutoConfiguration注解中的exclude/excludeName參數篩選一遍,再由AutoConfigurationImportFilter類所有實例篩選一遍,得到最終的用於Import的configuration和exclusion。
-
該函數是被誰調用的呢?在org.springframework.context.annotation.ConfigurationClassParser類中被processImports()調用,而processImports()函數被doProcessConfigurationClass()調用。下面從doProcessConfigurationClass() 看起。
-
上面為@Import()的Spring的注釋的底層實現
-
接着看我們EnableAutoConfiguration.class(類名)對應的值:
-
每一個這樣的
xxxAutoConfiguration
類都是容器中的一個組件,都加入到容器中;用他們來做自動配置; -
每一個自動配置類進行自動配置功能;
-
eg :HttpEncodingAutoConfiguration
package org.springframework.boot.autoconfigure.web.servlet;
......
//表示這是一個配置類,以前編寫的配置文件一樣,也可以給容器中添加組件
@Configuration(
proxyBeanMethods = false
)
/**
* 啟動指定類的ConfigurationProperties功能;
* 將配置文件中對應的值和HttpProperties綁定起來;
* 並把HttpProperties加入到ioc容器中
*/
@EnableConfigurationProperties({HttpProperties.class})
/**
* Spring底層@Conditional注解
* 根據不同的條件,如果滿足指定的條件,整個配置類里面的配置就會生效;
* 判斷當前應用是否是web應用,如果是,當前配置類生效
*/
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//判斷當前項目有沒有這個類
@ConditionalOnClass({CharacterEncodingFilter.class})
/**
* 判斷配置文件中是否存在某個配置 spring.http.encoding.enabled;如果不存在,判斷也是成立的
* 即使我們配置文件中不配置pring.http.encoding.enabled=true,也是默認生效的;
*/
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
//它已經和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一個有參構造器的情況下,參數的值就會從容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
@Bean //給容器中添加一個組件,這個組件的某些值需要從properties中獲取
@ConditionalOnMissingBean //判斷容器有沒有這個組件?(容器中沒有才會添加這個組件)
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
......
- 根據當前不同的條件判斷,決定這個配置類是否生效
- 一但這個配置類生效;這個配置類就會給容器中添加各種組件;這些組件的屬性是從對應的properties類中獲取的,這些類里面的每一個屬性又是和配置文件綁定的;
- 相應的配置類在@EnableConfigurationProperties中指定,其會在讀取我們寫的properties並將值注入類中,由
xxxAutoConfiguration
讀取 實例化的xxxxProperties類,根據屬性生成相應的bean給ApplicationContext調用。
所有在配置文件中能配置的屬性都是在xxxxProperties
類中封裝着;配置文件能配置什么就可以參照某個功能對應的這個屬性類
@ConfigurationProperties(
prefix = "spring.http"
)
public class HttpProperties {
private boolean logRequestDetails;
private final HttpProperties.Encoding encoding = new HttpProperties.Encoding();
我們配置時的流程:
SpringBoot啟動會加載大量的自動配置類
我們看我們需要的功能有沒有SpringBoot默認寫好的自動配置類
再來看這個自動配置類中到底配置了哪些組件;(只要我們要用的組件有,我們就不需要再來配置了)
給容器中自動配置類添加組件的時候,會從properties類中獲取某些屬性。我們就可以在配置文件中指定這些屬性的值
xxxxAutoConfigurartion
:自動配置類;
xxxxProperties
:封裝配置文件中相關屬性;
Apllication總體流程
SpringApplication的run方法的實現是我們本次旅程的主要線路,該方法的主要流程大體可以歸納如下:
1) 如果我們使用的是SpringApplication的靜態run方法,那么,這個方法里面首先要創建一個SpringApplication對象實例,然后調用這個創建好的SpringApplication的實例方法。在SpringApplication實例初始化的時候,它會提前做幾件事情:
- 根據classpath里面是否存在某個特征類(org.springframework.web.context.ConfigurableWebApplicationContext)來決定是否應該創建一個為Web應用使用的ApplicationContext類型。
- 使用SpringFactoriesLoader在應用的classpath中查找並加載所有可用的ApplicationContextInitializer。
- 使用SpringFactoriesLoader在應用的classpath中查找並加載所有可用的ApplicationListener。
- 推斷並設置main方法的定義類。
2) SpringApplication實例初始化完成並且完成設置后,就開始執行run方法的邏輯了,方法執行伊始,首先遍歷執行所有通過SpringFactoriesLoader可以查找到並加載的SpringApplicationRunListener。調用它們的started()方法,告訴這些SpringApplicationRunListener,“嘿,SpringBoot應用要開始執行咯!”。
3) 創建並配置當前Spring Boot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。
4) 遍歷調用所有SpringApplicationRunListener的environmentPrepared()的方法,告訴他們:“當前SpringBoot應用使用的Environment准備好了咯!”。
5) 如果SpringApplication的showBanner屬性被設置為true,則打印banner。
6) 根據用戶是否明確設置了applicationContextClass類型以及初始化階段的推斷結果,決定該為當前SpringBoot應用創建什么類型的ApplicationContext並創建完成,然后根據條件決定是否添加ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當然,最重要的,將之前准備好的Environment設置給創建好的ApplicationContext使用。
7) ApplicationContext創建好之后,SpringApplication會再次借助Spring-FactoriesLoader,查找並加載classpath中所有可用的ApplicationContext-Initializer,然后遍歷調用這些ApplicationContextInitializer的initialize(applicationContext)方法來對已經創建好的ApplicationContext進行進一步的處理。
8) 遍歷調用所有SpringApplicationRunListener的contextPrepared()方法。
9) 最核心的一步,將之前通過@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置加載到已經准備完畢的ApplicationContext。
10) 遍歷調用所有SpringApplicationRunListener的contextLoaded()方法。
11) 調用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。
12) 查找當前ApplicationContext中是否注冊有CommandLineRunner,如果有,則遍歷執行它們。
13) 正常情況下,遍歷執行SpringApplicationRunListener的finished()方法、(如果整個過程出現異常,則依然調用所有SpringApplicationRunListener的finished()方法,只不過這種情況下會將異常信息一並傳入處理)****