一、Spring Environment

Environment 是一种在容器内以 配置(Profile)和属性(Properties) 为模型的应用环境抽象整合。

这么说可能无法体现 Environment 的重要性。 我们可以吧 Spring 应用运行简单分为两部分:一、spring应用本身 二、spring 应用所处的环境 而 Environment 就是 spring 应用所处的环境的概念性建模。

  • 配置(Profile)

    在 Spring 容器中,Profile 是一种命名的 Bean 定义逻辑组 一个 Spring 应用可以同时激活多个 Profile,常见的使用场景如:应用部署环境(test、stage、production)、单元测试等

  • 应用程序可以通过调用 ConfigurableEnvironment 接口控制 Profile 的激活

    1. public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
    2. void setActiveProfiles(String... var1);
    3. void addActiveProfile(String var1);
    4. void setDefaultProfiles(String... var1);
    5. MutablePropertySources getPropertySources();
    6. Map<String, Object> getSystemProperties();
    7. Map<String, Object> getSystemEnvironment();
    8. void merge(ConfigurableEnvironment var1);
    9. }
  • 属性(Properties)

    属性又称之为配置项,Key-Value 的形式。 在 Spring 应用中常用作占用符(Placeolder),而在API 层面,Spring Framework 如下抽象来表述

    1. 组合属性:PropertySources
    2. 单一属性:PropertySource

二、Evnironment 继承结构

01.png

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

  1. public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
  2. private static final String DEFAULT_PROPERTIES = "defaultProperties";
  3. private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
  4. private static final String DEFAULT_NAMES = "application";
  5. public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
  6. public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
  7. public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
  8. public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
  9. public static final int DEFAULT_ORDER = -2147483638;
  10. public static final String APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME = "applicationConfigurationProperties";
  11. private final DeferredLog logger = new DeferredLog();
  12. private String searchLocations;
  13. private String names;
  14. private int order = -2147483638;
  15. ......
  16. private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
  17. List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
  18. postProcessors.add(this);
  19. AnnotationAwareOrderComparator.sort(postProcessors);
  20. Iterator var3 = postProcessors.iterator();
  21. while(var3.hasNext()) {
  22. EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
  23. postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
  24. }
  25. }
  26. ......
  27. }

ConfigFileApplicationListener 源码中,我们可以知道很多信息,例如:默认读取的配置文件路径,默认读取配置文件名称 等信息。

在前面的,我们谈到了 SpringApplication 中 run() 方法:

  1. public ConfigurableApplicationContext run(String... args) {
  2. ......
  3. ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
  4. ......
  5. }

前面我们提到过,bootstrap 的读取优先于,但是 他们都是通过 监听时间来执行的,那么怎么来确认,说谁更优先呢。那就是对比 order (越小越优先)【或者直接简单粗暴的针对 ConfigFileApplicationListener BootstrapApplicationListener 中的 onApplication 打断点,看谁的限执行】 BootstrapApplicationListener : order = - 2147483643 ConfigFileApplicationListener: order = - 2147483638

模拟测试

尝试获取环境配置文件中的配置属性

  1. @RestController
  2. public class Application {
  3. @Autowired
  4. private Environment environment;
  5. @Autowired
  6. private ApplicationContext context; // context 中有 Environment 属性
  7. @RequestMapping("/server-port")
  8. public String getPort(){
  9. return environment.getProperty("server.port");
  10. }
  11. @RequestMapping("/server-port2")
  12. public String getPort2(){
  13. return context.getEnvironment().getProperty("server.port");
  14. }
  15. }

模拟多个配置文件,根据加载顺序,根据覆盖的方式进行配置文件的加载

step1、在 resource 中增加 配置文件 application.properties

  1. server.port=9527

step2、在 resource/config 中 增加配置文件 application.properties

  1. ## 关闭安全验证
  2. management.security.enabled=false
  3. ## 端口配置
  4. server.port=9528

结论: 两个配置文件都正常读取, resource/config 中配置文件读取优先级低于 resoruce 下文件,并对 resource/ 下的配置文件进行覆盖.