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()方法,只不过这种情况下会将异常信息一并传入处理)****

上一篇:JVM初探(四):类加载器


下一篇:类加载器(ClassLoader)--运行时数据区