Springboot配置文件加載順序


配置文件加載順序

SpringBoot也可以從以下位置加載配置; 優先級從高到低;高優先級的配置覆蓋低優先級的配置,所有的配置會形成互補配置

  1. 命令行參數

    所有的配置都可以在命令行上進行指定

    java -jar xxx.jar --server.port=8087  --server.context-path=/abcCopy to clipboardErrorCopied
    

    多個配置用空格分開; --配置項=值

  2. 來自java:comp/env的JNDI屬性 ⤴️

  3. Java系統屬性(System.getProperties()) ⤴️

  4. 操作系統環境變量 ⤴️

  5. RandomValuePropertySource配置的random.*屬性值 ⤴️

由jar包外向jar包內進行尋找;

優先加載帶profile

  1. jar包外部的application-{profile}.propertiesapplication.yml(帶spring.profile)配置文件 ⤴️

  2. jar包內部的application-{profile}.propertiesapplication.yml(帶spring.profile)配置文件 ⤴️

再來加載不帶profile

  1. jar包外部的application.propertiesapplication.yml(不帶spring.profile)配置文件 ⤴️

  2. jar包內部的application.propertiesapplication.yml(不帶spring.profile)配置文件 ⤴️

  3. @Configuration注解類上的@PropertySource ⤴️

  4. 通過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注解的配置類有如下要求:

  1. @Configuration不可以是final類型;
  2. @Configuration不可以是匿名類;
  3. 嵌套的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;
    }

    ......
  1. 根據當前不同的條件判斷,決定這個配置類是否生效
  2. 一但這個配置類生效;這個配置類就會給容器中添加各種組件;這些組件的屬性是從對應的properties類中獲取的,這些類里面的每一個屬性又是和配置文件綁定的;
  3. 相應的配置類在@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()方法,只不過這種情況下會將異常信息一並傳入處理)****


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM