一、Spring Environment
Environment 是一种在容器内以 配置(Profile)和属性(Properties) 为模型的应用环境抽象整合。
这么说可能无法体现 Environment 的重要性。 我们可以吧 Spring 应用运行简单分为两部分:一、spring应用本身 二、spring 应用所处的环境 而 Environment 就是 spring 应用所处的环境的概念性建模。
配置(Profile)
在 Spring 容器中,Profile 是一种命名的 Bean 定义逻辑组 一个 Spring 应用可以同时激活多个 Profile,常见的使用场景如:应用部署环境(test、stage、production)、单元测试等
应用程序可以通过调用 ConfigurableEnvironment 接口控制 Profile 的激活
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
void setActiveProfiles(String... var1);
void addActiveProfile(String var1);
void setDefaultProfiles(String... var1);
MutablePropertySources getPropertySources();
Map<String, Object> getSystemProperties();
Map<String, Object> getSystemEnvironment();
void merge(ConfigurableEnvironment var1);
}
属性(Properties)
属性又称之为配置项,Key-Value 的形式。 在 Spring 应用中常用作占用符(Placeolder),而在API 层面,Spring Framework 如下抽象来表述
组合属性:PropertySources
单一属性:PropertySource
二、Evnironment 继承结构
PropertyResolver | 读取属性,解析占位符,将读取到的属性转换成指定类型 |
---|---|
Environment | 继承自PropertyResolver,对环境属性访问和default/active profile访问的抽象 具备PropertyResolver提供的所有能力 |
ConfigurablePropertyResolver | 接口,为PropertyResolver 接口抽象的属性源访问做了配置方面的增强。 |
ConfigurableEnvironment | 接口,在所继承的接口之上增加了设置defaut/active profile 的能力,增加/删除环境对象中属性源的能力 |
ConfigurableWebEnvironment | 接口,向接口ConfigurableEnvironment 增强了根据Servlet上下文/配置初始化属性源的能力 |
AbstractEnvironment | Environment抽象基类,实现了 ConfigurableEnvironment |
StandardEnvironment | 实现类,针对标准Spring应用(非Web应用)环境, 在 AbstractEnvironment 基础上提供了属性源systemEnvironment (来自System.getenv() )和systemProperties (来自System.getProperties() ) |
StandardServletEnvironment | 实现类,针对标准Spring Servlet Web应用的环境, 在 StandardEnvironment 的基础上增加了servletContextInitParams /servletConfigInitParams /jndiProperties 三个属性源 |
配置文件在何时被读取的
application.properties 配置文件读取一样是通过 事件驱动机制 来实现的。 通过ConfigFileApplicationListener 监听 ApplicationEnvironmentPreparedEvent 进行处理。
我们先来看一下 ConfigFileApplicationListener
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
private static final String DEFAULT_PROPERTIES = "defaultProperties";
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
private static final String DEFAULT_NAMES = "application";
public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
public static final int DEFAULT_ORDER = -2147483638;
public static final String APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME = "applicationConfigurationProperties";
private final DeferredLog logger = new DeferredLog();
private String searchLocations;
private String names;
private int order = -2147483638;
......
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
Iterator var3 = postProcessors.iterator();
while(var3.hasNext()) {
EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
......
}
在 ConfigFileApplicationListener 源码中,我们可以知道很多信息,例如:默认读取的配置文件路径,默认读取配置文件名称 等信息。
在前面的,我们谈到了 SpringApplication 中 run() 方法:
public ConfigurableApplicationContext run(String... args) {
......
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
......
}
前面我们提到过,bootstrap 的读取优先于,但是 他们都是通过 监听时间来执行的,那么怎么来确认,说谁更优先呢。那就是对比 order (越小越优先)【或者直接简单粗暴的针对 ConfigFileApplicationListener BootstrapApplicationListener 中的 onApplication 打断点,看谁的限执行】 BootstrapApplicationListener : order = - 2147483643 ConfigFileApplicationListener: order = - 2147483638
模拟测试
尝试获取环境配置文件中的配置属性
@RestController
public class Application {
@Autowired
private Environment environment;
@Autowired
private ApplicationContext context; // context 中有 Environment 属性
@RequestMapping("/server-port")
public String getPort(){
return environment.getProperty("server.port");
}
@RequestMapping("/server-port2")
public String getPort2(){
return context.getEnvironment().getProperty("server.port");
}
}
模拟多个配置文件,根据加载顺序,根据覆盖的方式进行配置文件的加载
step1、在 resource 中增加 配置文件 application.properties
server.port=9527
step2、在 resource/config 中 增加配置文件 application.properties
## 关闭安全验证
management.security.enabled=false
## 端口配置
server.port=9528
结论: 两个配置文件都正常读取, resource/config 中配置文件读取优先级低于 resoruce 下文件,并对 resource/ 下的配置文件进行覆盖.