Spring 读取 Properties 实现原理

Spring 读取 Properties 实现原理

public class SpringApplication {

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(bootstrapContext, environment);

        // ...
    }

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        // ...
		configurePropertySources(environment, args);
		configureProfiles(environment, args);
	}

    private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}

}

上述代码可知,SpringApplication 在启动的时候先创建 Environment,然后再配置 Profiles,然后再添加 configurationProperties。一般情况下创建的都是 StandardServletEnvironment 环境。

| Create Environment & Configure Profiles | --> | Attach Configure Properties |

(1) Create Environment & Configure Profiles

StandardServletEnvironment 这个类位于 Spring 项目下,它默认在构造器内部就执行 customizePropertySources 方法读取相关的 properties 文件。

它的定义及其相关父类的定义如下:

public class StandardEnvironment extends AbstractEnvironment {

	/** System environment property source name: {@value}. */
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	/** JVM system properties property source name: {@value}. */
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

    @Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}

}

// 构造器内部就开始自定义 property sources
public abstract class AbstractEnvironment implements ConfigurableEnvironment {

    public AbstractEnvironment() {
		customizePropertySources(this.propertySources);
	}

}

可见如果 ENV 或者 System.properties 中有 spring.profiles.active 的话,那么 SpringApplication 在启动的时候,就会第一次读取到 spring.profiles.active 的值。然后在 Spring-Boot-2.0.6 版本中会通过 environment.setActiveProfiles() 方法告诉 environment 现在激活的 profile;但是在 v2.5.0-M1 版本中,并没有告诉现在激活的 profile

(2) Attach Configure Properties

Attach configurationProperties 的过程如下:

public final class ConfigurationPropertySources {

	private static final String ATTACHED_PROPERTY_SOURCE_NAME = "configurationProperties";

    public static void attach(Environment environment) {
		MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
		PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
        
        // ...

		if (attached == null) {
			sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
					new SpringConfigurationPropertySources(sources)));
		}
	}

}

这个 configurationProperties 的数据源就是本地 application.propertiesapplication.yml 类似于这些的文件源。

读取 Property 过程

getProperty 背后是通过 propertyResolver 实现的:

public abstract class AbstractEnvironment implements ConfigurableEnvironment {

	private final MutablePropertySources propertySources = new MutablePropertySources();

    private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);

    @Override
	@Nullable
	public String getProperty(String key) {
		return this.propertyResolver.getProperty(key);
	}

}

找到就直接返回:

public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {

    @Nullable
	protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
		if (this.propertySources != null) {
			for (PropertySource<?> propertySource : this.propertySources) {
				Object value = propertySource.getProperty(key);
				if (value != null) {
                    // ...
					return convertValueIfNecessary(value, targetValueType);
				}
			}
		}

		return null;
	}


}

Properties 文件加载顺序

配置文件优先级从高到低顺序↓

  • file:./config/ - 优先级最高(项目根路径下的config)
  • file:./ - 优先级第二 -(项目根路径下)
  • classpath:/config/ - 优先级第三(项目resources/config下)
  • classpath:/ - 优先级第四(项目resources根目录)

SpringBoot 项目启动会去扫面项目以上目录位置的 application.ymlapplication.properties 文件。

以上位置的 application.ymlapplication.properties 遵循:

  • 高优先级配置会覆盖低优先级配置
  • 多个配置文件互补

SpringBoot 外化配置

参考