SpringBoot之属性配置解析

属性配置介绍

  • Devtools全局配置
  • 测试环境的@TestPropertySource注解
  • 测试环境properties属性
  • 命令行参数
  • 命令行参数
  • ServletConfig初始化参数
  • ServletContext初始化参数
  • JNDI属性
  • JAVA系统属性
  • 操作系统的环境变量
  • RandomValuePropertySource随机值属性
  • jar包外的application-{profile}.properties
  • jar包内的application-{profile}.properties
  • jar包外的application.properties
  • jar包内的application.properties
  • @PropertySource绑定配置
  • 默认属性
    SpringBoot主要有上面几种配置方式,从上到下优先级递减,优先级高的会覆盖优先级低的配置。

Environment解析

在run方法中,prepareEnvironment按字面意思就是准备环境

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

进入prepareEnvironment();

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

分析getOrCreateEnvironment();

	// 获取或创建Environment
private ConfigurableEnvironment getOrCreateEnvironment() {
    // 存在则直接返回
    if (this.environment != null) {
        return this.environment;
    }
    // 根据webApplicationType创建对应的Environment
    switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment(); // 标准的Servlet环境,也就是我们说的web环境
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();// 标准环境,非web环境
		}
}

我们这里是web环境,所以返回的是StandardServletEnvironment对象。看一下StandardServletEnvironment的uml图。
在这里插入图片描述
StandardServletEnvironment继承自StandardEnvironment,而StandardEnvironment又继承了AbstractEnvironment,AbstractEnvironment的无参构造方法中调用了customizePropertySources方法,也就说StandardServletEnvironment在实例化的时候,他的customizePropertySources会被调用,customizePropertySources源代码如下

@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		super.customizePropertySources(propertySources);
	}

customizePropertySources方法往propertySources中添加了两个名字叫servletConfigInitParams、servletContextInitParams的StubPropertySource对象,没更多的操作;而StandardEnvironment的customizePropertySources方法则往propertySources中添加了两个包含java系统属性和操作系统环境变量的两个对象:MapPropertySource和SystemEnvironmentPropertySource。
总结下,getOrCreateEnvironment方法创建并返回了一个环境:StandardServletEnvironment
在这里插入图片描述
分析configureEnvironment()

configureEnvironment(environment, applicationArguments.getSourceArgs());

进入configureEnvironment

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
		if (this.addConversionService) {
			//添加属性转换相关的成员
			ConversionService conversionService = ApplicationConversionService.getSharedInstance();
			environment.setConversionService((ConfigurableConversionService) conversionService);
		}
		configurePropertySources(environment, args);
		configureProfiles(environment, args);
	}

configurePropertySources

	protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
		MutablePropertySources sources = environment.getPropertySources();
		//这里添加通过硬编码的属性
		if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
			sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
		}
		//添加通过命令行参数设置的属性,解析它并封装进SimpleCommandLinePropertySource对象,同时将此对象放到sources的第一位置(优先级最高)
		if (this.addCommandLineProperties && args.length > 0) {
			String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
			//判断是否已经添加过命令行的属性元
			if (sources.contains(name)) {
				PropertySource<?> source = sources.get(name);
				CompositePropertySource composite = new CompositePropertySource(name);
				composite.addPropertySource(
						new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
				composite.addPropertySource(source);
				sources.replace(name, composite);
			}
			else {
				sources.addFirst(new SimpleCommandLinePropertySource(args));
			}
		}
	}

configureProfiles

	protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
		environment.getActiveProfiles(); // ensure they are initialized
		// But these ones should go first (last wins in a property key clash)
		Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
		profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
		environment.setActiveProfiles(StringUtils.toStringArray(profiles));
	}

分析listeners.environmentPrepared(environment);

public void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

我们可以看有7个监听器对ApplicationEnvironmentPreparedEvent事件感兴趣,下面逐一分析每个监听器。
在这里插入图片描述

  • ConfigFileApplicationListener:
public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}

进入onApplicationEnvironmentPreparedEvent

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}

1、加载EnvironmentPostProcessor列表,仍然是从META-INF/spring.factories中加载(在SpringApplication实例化的时候已经加载了,这次是从缓存中读取),然后实例化;
2、将自己也加入EnvironmentPostProcessor列表;ConfigFileApplicationListener实现了EnvironmentPostProcessor接口,可以看它的类图。
3、对EnvironmentPostProcessor列表进行排序;排序之后,EnvironmentPostProcessor列表图如下:
在这里插入图片描述
4、遍历EnvironmentPostProcessor列表,调用每个EnvironmentPostProcessor的postProcessEnvironment方法

SystemEnvironmentPropertySourceEnvironmentPostProcessor

将propertySourceList中名为systemEnvironment的SystemEnvironmentPropertySource对象替换成OriginAwareSystemEnvironmentPropertySource对象,source未变,还是SystemEnvironmentPropertySource对象的source;OriginAwareSystemEnvironmentPropertySource是SystemEnvironmentPropertySourceEnvironmentPostProcessor的静态内部类,且继承自SystemEnvironmentPropertySource。具体这么替换出于什么目的,便于原点查找?暂时还未知。

SpringApplicationJsonEnvironmentPostProcessor

spring.application.json(或SPRING_APPLICATION_JSON)是设置在系统属性或系统环境中;

如果spring.application.json(或SPRING_APPLICATION_JSON)有配置,那么给environment的propertySourceList增加JsonPropertySource,并将JsonPropertySource放到名叫systemProperties的PropertySource前;目前没有配置,那么此环境后处理器相当于什么也没做。

CloudFoundryVcapEnvironmentPostProcessor

云平台是否激活,激活了则给environment的propertySourceList增加名为vcap的PropertiesPropertySource对象,并将此对象放到命令行参数PropertySource(名叫commandLineArgs)后。很显然,我们没有激活云平台,那么此环境后处理器相当于什么也没做。

ConfigFileApplicationListener

添加名叫random的RandomValuePropertySource到名叫systemEnvironment的PropertySource后;

并初始化Profiles;初始化PropertiesPropertySourceLoader和YamlPropertySourceLoader这两个加载器从file:./config/,file:./,classpath:/config/,classpath:/路径下加载配置文件,PropertiesPropertySourceLoader加载配置文件application.xml和application.properties,YamlPropertySourceLoader加载配置文件application.yml和application.yaml。目前我们之后classpath:/路径下有个application.yml配置文件,将其属性配置封装进了一个名叫applicationConfig:[classpath:/application.yml]的OriginTrackedMapPropertySource中,并将此对象放到了propertySourceList的最后。
(转载这位大神的)
其他lisenter作用不大就不逐一分析了
分析bindToSpringApplication(environment);

protected void bindToSpringApplication(ConfigurableEnvironment environment) {
		try {
			Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
		}
		catch (Exception ex) {
			throw new IllegalStateException("Cannot bind to SpringApplication", ex);
		}
	}

这个方法主要是从environment中获取所有以spring.main开头的属性,绑定到这个SpringApplication对应的属性当中。例如下面这些:
在这里插入图片描述
ConfigurationPropertySources.attach(environment);

	public static void attach(Environment environment) {
		Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
		MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
		PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
		if (attached != null && attached.getSource() != sources) {
			sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
			attached = null;
		}
		if (attached == null) {
			sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
					new SpringConfigurationPropertySources(sources)));
		}
	}

将sources封装成了一个名叫configurationProperties的ConfigurationPropertySourcesPropertySource对象,并把这个对象放到了sources的第一个位置。SpringConfigurationPropertySources是一个将MutablePropertySources转换成ConfigurationPropertySources的适配器。
在这里插入图片描述

spring profile介绍

<p>A <em>profile</em> is a named, logical group of bean definitions to be registered
 * with the container only if the given profile is <em>active</em>. Beans may be assigned
 * to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema
 * or the {@link org.springframework.context.annotation.Profile @Profile} annotation for
 * syntax details. The role of the {@code Environment} object with relation to profiles is
 * in determining which profiles (if any) are currently {@linkplain #getActiveProfiles
 * active}, and which profiles (if any) should be {@linkplain #getDefaultProfiles active
 * by default}.

Spring中的Profile功能其实早在Spring 3.1的版本就已经出来,它可以理解为我们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中。举个更具体的例子,我们以前所定义的Bean,当Spring容器一启动的时候,就会一股脑的全部加载这些信息完成对Bean的创建;而使用了Profile之后,它会将Bean的定义进行更细粒度的划分,将这些定义的Bean划分为几个不同的组,当Spring容器加载配置信息的时候,首先查找激活的Profile,然后只会去加载被激活的组中所定义的Bean信息,而不被激活的Profile中所定义的Bean定义信息是不会加载用于创建Bean的。
上面的解析转载简书
激活profile
可以通过spring.profiles.active=**来设置,需要注意的是spring.profiles.active跟spring.profiles.defual互斥,如果需要配置多个profiles,可以通过spring.profiles.include= X, X 来设置。profile的原理暂时不解析了。

这篇博客主要参考:参考

发布了6 篇原创文章 · 获赞 0 · 访问量 274

猜你喜欢

转载自blog.csdn.net/weixin_43960292/article/details/105388907
今日推荐