# Spring Environment

## 1. Environment概述

先对 `Environment` 有一个大体的认识吧。`Environment` 是从 SpringFramework 3.1 开始引入的一个抽象模型，至于抽象模型，和具体的理解，我想小伙伴们可以先自行思索一下。

### 1.1 第一感觉

其实第一眼看到这个名词，我们就应该有一个模糊的猜想了，它应该是基于 SpringFramework 的工程的**运行时环境**。所以我们可以这样看待我们编写的基于 SpringFramework 的应用程序：

![img](https://image.ldbmcs.com/YJESK7.jpg)

这个理解是否正确呢？我们可以去官方文档加以验证。

### 1.2 官方文档

当我们去翻 SpringFramework 的官方文档时，会发现官方是这样概述 `Environment` 的：

[Environment Abstraction](https://link.juejin.cn/?target=https%3A%2F%2Fdocs.spring.io%2Fspring%2Fdocs%2F5.2.x%2Fspring-framework-reference%2Fcore.html%23beans-environment)

The `Environment` interface is an abstraction integrated in the container that models two key aspects of the application environment: profiles and properties. A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or with annotations. The role of the `Environment` object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default . Properties play an important role in almost all applications and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc `Properties` objects, `Map` objects, and so on. The role of the `Environment` object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.

`Environment` 接口是集成在容器中的抽象，可对应用程序环境的两个关键方面进行建模：`Profile` 和 `properties` 。 Profiles 是仅在指定 profile 处于活动状态（ active ）时才向容器注册 `BeanDefinition` 的命名逻辑组。它可以将 Bean 分配给不同的 profile （无论是以 XML 定义还是注解配置）。与配置文件相关的 `Environment` 作用是确定哪些配置文件当前处于活动状态，以及哪些配置文件在默认情况下应处于活动状态。 Properties 在几乎所有应用程序中都起着重要作用，并且可能源自多种来源：属性文件，JVM 系统属性，系统环境变量，JNDI，`ServletContext` 参数，临时属性对象，`Map` 对象等。`Environment` 与属性相关联的作用是为用户提供方便的接口，它可以用于配置属性源，并从 `Environment` 中解析属性。

讲道理这段话理解起来不是那么容易，不过第一句【`Environment` 是集成在容器中的抽象】，会让我们产生一种感觉：前面的理解是不是出现了一些偏差？如果按照官方文档的说法，`Environment` 与工程的结构应该是这样才对：

![img](https://image.ldbmcs.com/rFq8er.jpg)

到底是不是这样呢，根据个人的理解不同，表达出来的也会不太一样。

### 1.3 小册一家之言

作者个人是倾向于如下的结构理解，这样解释起来会相对更合理一些：

* 首先，`Environment` 中包含 profiles 和 properties ，这些配置信息会影响 IOC 容器中的 bean 的注册与创建；
* 其次，`Environment` 的创建是在 `ApplicationContext` 创建后才创建的（ IOC 原理部分会解释），所以 `Environment` 应该是伴随着 `ApplicationContext` 的存在而存在；
* 第三，`ApplicationContext` 中同时包含 `Environment` 和组件 bean ，而且从 `BeanFactory` 的视角来看，`Environment` 也是一个 Bean ，只不过它的地位比较特殊。

基于这三点，`Environment` 与工程的结构应该是下图这样的：

![img](https://image.ldbmcs.com/EKFXkN.jpg)

理解了两者的结构及关系，再来回头看看 `Environment` 的组成部分：**profiles 和 properties** ，咱之前也都了解过了，所以 `Environment` 的整体理解也就相对没有那么难了吧！

### 1.4 javadoc中的描述

上面并没有引用 `Environment` 的 javadoc 来阐述 `Environment` 的概念和定义，原因是 javadoc 并没有对 Application 和 `Environment` 之间的关系进行描述，所以小册选择在这里再贴出。

由于 javadoc 的篇幅太长，咱们拆解开来看。

#### 1.4.1 Environment包含profile与properties

nterface representing the environment in which the current application is running. Models two key aspects of the application environment: profiles and properties. Methods related to property access are exposed via the `PropertyResolver` superinterface.

`Environment` 是表示当前应用程序正在其中运行的环境的接口。它为应用环境制定了两个关键的方面：**profile** 和 **properties**。与属性访问有关的方法通过 `PropertyResolver` 这个父接口公开。

这一段也是总体的概括 `Environment` 的基本设计和作用，不过它又提到了 `PropertyResolver` 这个接口，这个接口负责解析占位符（ **${...}** ）对应的值，作用也比较容易理解，这里就不多展开解释啦。

#### 1.4.2 profile用于区分不同的环境模式

A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the `spring-beans 3.1 schema` or the `@Profile` annotation for syntax details. The role of the `Environment` object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default. profile 机制保证了仅在给定 profile 处于激活状态时，才向容器注册的 `BeanDefinition` 的命名逻辑组。无论是用 XML 定义还是通过注解定义，都可以将 Bean 分配给指定的 profile。有关语法的详细信息，请参见 `spring-beans 3.1规范文档` 或 `@Profile` 注解。`Environment` 的作用是决定当前哪些配置文件（如果有）处于活动状态，以及默认情况下哪些配置文件（如果有）应处于活动状态。

通过前面的学习，这段文档注释也就不难理解了吧。`Environment` 配合 profile 可以完成**指定模式的环境的组件装配**，以及不同的配置属性注入。

#### 1.4.3 properties用于配置属性和注入值

Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them. `Properties` 在几乎所有应用程序中都起着重要作用，并且可能来源自多种途径：属性文件，JVM 系统属性，系统环境变量，JNDI，`ServletContext` 参数，临时属性对象，Map等。`Environment` 与 `Properties` 的关系是为用户提供方便的服务接口，以配置属性源，并从中解析属性值。

上一章的配置元信息中我们已经知道 properties 的最大作用之一是做**外部化配置**，`Environment` 中存放了很多 properties ，它们的来源有很多种，而最终的作用都是**提供了属性配置**，或者**给组件注入属性值**。

#### 1.4.4 Environment不建议直接使用

Beans managed within an `ApplicationContext` may register to be `EnvironmentAware` or `@Inject` the Environment in order to query profile state or resolve properties directly. In most cases, however, application-level beans should not need to interact with the Environment directly but instead may have to have ${...} property values replaced by a property placeholder configurer such as `PropertySourcesPlaceholderConfigurer`, which itself is EnvironmentAware and as of Spring 3.1 is registered by default when using `<context:property-placeholder/>`.

在 `ApplicationContext` 中管理的 Bean 可以注册为 `EnvironmentAware` 或使用 `@Inject` 标注在 `Environment` 上，以便直接查询 profile 的状态或解析 `Properties`。 但是，在大多数情况下，应用程序级 Bean 不必直接与 `Environment` 交互，而是通过将 **${...}** 属性值替换为属性占位符配置器进行属性注入（例如 `PropertySourcesPlaceholderConfigurer`），该属性本身是 `EnvironmentAware`，当配置了 `<context:property-placeholder/>` 时，默认情况下会使用 Spring 3.1 的规范注册。

这一段的描述主要讲了两件事情：`Environment` 可以注入到组件中，用于获取当前环境激活的所有 profile 模式；但是又不推荐开发者直接使用它，而是通过占位符注入配置属性的值。为什么会这么说呢，其实这个又要说回 `Environment` 设计的原始意图。`Environment` 的设计本身就应该是一个**不被应用程序接触到的 “环境”** ，我们**只能从环境中获取一些它已经有的信息，但不应该获取它本身**。所以，在处理 properties 的获取时，直接使用占位符就可以获取了。

#### 1.4.5 ApplicationContext获取到的是ConfigurableEnvironment

Configuration of the environment object must be done through the `ConfigurableEnvironment` interface, returned from all `AbstractApplicationContext` subclass `getEnvironment()` methods. See `ConfigurableEnvironment Javadoc` for usage examples demonstrating manipulation of property sources prior to application context `refresh()` .

必须通过从所有 `AbstractApplicationContext` 子类的 `getEnvironment()` 方法返回的 `ConfigurableEnvironment` 接口完成环境对象的配置。请参阅 `ConfigurableEnvironment` 的 javadoc 以获取使用示例，这些示例演示在应用程序上下文 `refresh()` 方法被调用之前对属性源进行的操作。

注意这里，`ApplicationContext` 的根实现类 `AbstractApplicationContext` 获取到的是 `ConfigurableEnvironment` ，它具有 **“可写”** 的特征，换言之我们可以修改它内部的属性值 / 数据。不过话又说回来，通常情况下我们都不会直接改它，除非要对 SpringFramework 应用的启动流程或者运行中进行一些额外的扩展或者修改。

到这里，整个 javadoc 也就读完了，最后总结一下吧，这部分最终还是要理解，并且最好用自己的话概括出来。

### 1.5 总结

`**Environment**` **是 SpringFramework 3.1 引入的抽象的概念，它包含 profiles 和 properties 的信息，可以实现统一的配置存储和注入、配置属性的解析等。其中 profiles 实现了一种基于模式的环境配置，properties 则应用于外部化配置。**

## 2. Environment的结构

了解了概念和设计的思想，下面咱来看看 `Environment` 在 SpringFramework 中设计的结构。

借助 IDEA ，可以看到 `Environment` 的上下级继承和派生关系：

![img](https://image.ldbmcs.com/91YVhp.jpg)

这里面的几个重要的接口和类关注一下。

### 2.1 PropertyResolver

这个接口，只从接口名就知道它应该是处理占位符 **${}** 的。观察接口的方法定义，直接实锤了它就是做配置属性值的获取和解析的：（下面是 `PropertyResolver` 的部分方法定义）

```java
public interface PropertyResolver {

    // 检查所有的配置属性中是否包含指定key
    boolean containsProperty(String key);

    // 以String的形式返回指定的配置属性的值
    String getProperty(String key);

    // 带默认值的获取
    String getProperty(String key, String defaultValue);

    // 指定返回类型的配置属性值获取
    <T> T getProperty(String key, Class<T> targetType);

    // ......

    // 解析占位符
    String resolvePlaceholders(String text);

    // ......
}
```

所以由此也就证明了：`**Environment**` **可以获取配置元信息，同时也可以解析占位符的信息**。

### 2.2 ConfigurableEnvironment

老套路了，看到 **Configurable** 开头，立马应该想到它又扩展了什么 set 方法吧！果然，可以从接口的方法定义中看到这样的方法名：`setActiveProfiles` 、`addActiveProfile` 。。。好了不用说了，这个接口一定可以**用编程式设置 profile** ！

除此之外，这个接口中还有一个方法比较值得注意：

```java
MutablePropertySources getPropertySources();
```

看方法名可以知道它会返回所有的 `PropertySource` 对象，可是 `MutablePropertySources` 是个什么鬼呢？点开它的源码，发现它的内部就是一个 **List** ：

```java
public class MutablePropertySources implements PropertySources {
	private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
}
```

由此可以总结出一个小小的结论：**Mutable 开头的类名，通常可能是一个类型的 List 组合封装**。

### 2.3 StandardEnvironment

它是 SpringFramework 中默认使用的标准运行时环境的抽象实现，不过它里面的方法实现的非常少，基本都是由 `AbstractEnvironment` 负责实现。在后面的原理部分，我们还会再见到它的，这里先留意一下就好。

## 3. Environment的基本使用

虽说 `Environment` 不建议直接在应用程序中使用，但是部分场景下还是需要直接接触它来操纵。本节小册不会直接介绍 `Environment` 的实际使用，而是先带小伙伴用最简单的方式用一用，体会一下 `Environment` 的作用即可。

### 3.1 获得Environment的API

既然 `Environment` 存在于 `ApplicationContext` 中，那么获取 `Environment` 的方式自然也就可以想到：`@Autowired` 就可以吧！下面咱来实际操作一下。

任意编写一个 Bean ，并声明注入 `Environment` ：

```java
@Component
public class EnvironmentHolder {
    
    @Autowired
    Environment environment;
    
    public void printEnvironment() {
        System.out.println(environment);
    }
}
```

之后直接使用包扫描的方式，驱动 `AnnotationConfigApplicationContext` ，就可以取到刚写的这个 `EnvironmentHolder` 了：

```java
public class EnvironmentQuickstartApplication {
    
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
                "com.linkedbear.spring.environment.a_quickstart.bean");
        EnvironmentHolder environmentHolder = ctx.getBean(EnvironmentHolder.class);
        environmentHolder.printEnvironment();
    }
}
```

运行 `main` 方法，控制台会打印出 `Environment` 的对象信息：

```java
StandardEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[PropertiesPropertySource {name='systemProperties'}, SystemEnvironmentPropertySource {name='systemEnvironment'}]}
```

除此之外，联想到 `BeanFactory` 、`ApplicationContext` 的注入方式还有**回调注入**，作为 SpringFramework 的内置 API ，估计也会有一个 **Aware** 回调注入的接口吧！那自然是必须的，`EnvironmentAware` 就是回调注入的接口，小伙伴们可以自行实现操作一下，小册这里就不示范了（实在是简单的一批）。

注：使用 `@Autowired` 的方式在某些情况下会注入失败，所以对于小伙伴们而言，注入是否能成功需要亲手测试运行检验才能知道。在后面的后置处理器部分，会演示一种无法使用 `@Autowired` 注入 `Environment` 的方式，小伙伴们到时候可以留意一下。

### 3.2 使用Environment获取配置属性的值

既然上面已经获取到 `Environment` 了，那操作 `Environment` 的方法自然也就不是问题了。

为了方便获取 properties 的配置信息，这里编写一个配置类，把上一章 `PropertySource` 的 `jdbc.properties` 加载进去：

```java
@Configuration
@ComponentScan("com.linkedbear.spring.environment.b_api.bean")
@PropertySource("propertysource/jdbc.properties")
public class EnvironmentPropertyConfiguration {
    
}
```

之后使用该配置类驱动 IOC 容器即可。

`EnvironmentHolder` 中，这次我们取一下默认的 profiles ，以及 `jdbc.properties` 中的配置属性值：

```java
@Component
public class EnvironmentHolder {
    
    @Autowired
    Environment environment;
    
    public void printEnvironment() {
        System.out.println(Arrays.toString(environment.getDefaultProfiles()));
        System.out.println(environment.getProperty("jdbc.url"));
    }
}
```

重新驱动 IOC 容器，并取出 `EnvironmentHolder` 执行 `printEnvironment` 方法，控制台打印如下信息：

```
[default]
jdbc:mysql://localhost:3306/test
```

至此，`Environment` 的功能已正常使用。

`Environment` 中其他的 API ，小伙伴都可以自己动手使用一下，小册就不展开举例了。下面我们来向更深层次研究几个问题。

## 4. Environment深入探讨

先注意一下上面控制台打印的默认 profiles ，发现它有一个默认值是 **default** ，它是从哪来的呢？我们又没声明呀！

### 4.1 Environment的默认profiles

想知道 profiles 的默认配置，就要进入到 `Environment` 的抽象实现 `AbstractEnvironment` 中了：

```java
@Override
public String[] getDefaultProfiles() {
    return StringUtils.toStringArray(doGetDefaultProfiles());
}
```

看，这里有一个很有意思的操作：`getDefaultProfiles` 调用了 `doGetDefaultProfiles` 方法，**这个设计在 SpringFramework 中大量出现和使用**！

#### 4.1.1 SpringFramework中的方法命名规范

在 SpringFramework 的框架编码中，如果有出现一个方法是 do 开头，并且去掉 do 后能找到一个与剩余名称一样的方法，则代表如下含义：**不带 do 开头的方法一般负责前置校验处理、返回结果封装，带 do 开头的方法是真正执行逻辑的方法（如** `**getBean**` **方法的底层会调用** `**doGetBean**` **来真正的寻找 IOC 容器的 bean ，**`**createBean**` **会调用** `**doCreateBean**` **来真正的创建一个 bean ）。**

#### 4.1.2 doGetDefaultProfiles的实现

```java
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";

protected Set<String> doGetDefaultProfiles() {
    synchronized (this.defaultProfiles) {
        // 取框架默认的profiles，并与当前的对比
        if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
            // 如果一致，则尝试从Environment中获取显式声明的profiles
            String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
            // 如果有显式声明，则覆盖原有的默认值
            if (StringUtils.hasText(profiles)) {
                setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
                        StringUtils.trimAllWhitespace(profiles)));
            }
        }
        return this.defaultProfiles;
    }
}
```

看这个方法的实现，整体逻辑也不算复杂，关键是看它取框架默认的 profiles ，它其实就是取的 `AbstractEnvironment` 中内置的常量：

```java
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";

protected Set<String> getReservedDefaultProfiles() {
    return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
}
```

由此，可知默认的 default 来源。

#### 4.1.3 覆盖默认的profiles方法

上面的源码中，也可以看到，我们可以通过声明 `**spring.profiles.default**` 的配置，来覆盖 SpringFramework 中原有的默认 profiles ，一个比较常用的方法是在 jvm 的启动参数上添加：

![img](https://image.ldbmcs.com/MRQ0Nv.jpg)

在 IDEA 的启动配置中，声明 VM options 就可以指定默认的 profiles 了。

同理，指定激活的 profiles 也可以像这样指定，只不过它的参数名称为 `**spring.profiles.active**` 。

### 4.2 Environment解析properties的底层

前面看 `Environment` 的结构，我们已经知道 `Environment` 继承了父接口 `PropertyResolver` ，自然它拥有解析配置元信息的能力，它的底层是如何实现的呢？是 `Environment` 自己干活，还是...有其人？

#### 4.2.1 PropertyResolver的实现类

借助 IDE ，翻看 PropertyResolver 的子接口和实现类，发现仅仅就这么几个而已：

![img](https://image.ldbmcs.com/e20WKk.jpg)

自然地，我们要去找 `Environment` 的实现类，`StandardEnvironment` ，看它是如何解析配置属性值的。

#### 4.2.2 getProperty的实现是委派

翻看 `StandardEnvironment` ，发现 `getProperty` 方法并没有在此实现，而是父类 `AbstractEnvironment` 中，但是实现类中发现它是直接调用了自身组合的一个 `ConfigurablePropertyResolver` 来处理：

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

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

一般的，我们称这种方式叫做 **“委派”** ，它与代理、装饰者不同：**委派仅仅是将方法的执行转移给另一个对象，而代理可能会在此做额外的处理，装饰者也会在方法执行前后做增强**。

继续往里看，就要进入 `PropertySourcesPropertyResolver` 的底层来研究了，小册认为再往里研究的性价比相对就不高了：这里面的解析逻辑相对复杂，但搞明白后的收益并不大，综合来看不太适合再深入研究。小伙伴们只需要了解 `Environment` 的解析配置属性值的底层是交给 `PropertySourcesPropertyResolver` 来处理就好啦。
