ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Springboot配置文件加载顺序

2020-08-08 15:03:39  阅读:281  来源: 互联网

标签:Springboot 配置文件 spring classLoader 配置 class properties 加载


配置文件加载顺序

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

标签:Springboot,配置文件,spring,classLoader,配置,class,properties,加载
来源: https://www.cnblogs.com/eternal-heathens/p/13457811.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有