[Java] 深入理解:Spring Environment

1 概述:Spring Environment

1.0 简介

  • Spring Framework 3.1 开始引入 Environment 抽象,它统一 Spring 配置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer,而且还支持更丰富的配置属性源(PropertySource)。

  • ConfigurableEnvironment 是什么?

    • ConfigurableEnvironment 是 Environment接口的一个实现,它提供了更多的配置选项。这意味着,除了可以读取和查询属性外,您还可以添加、删除或修改 PropertySource(属性源)。
  • PropertySource 是什么?

    • PropertySource 是属性的来源。它可以是配置文件(如 application.properties 或 application.yml)、系统环境变量、命令行参数等。每个 PropertySource 都有一个唯一的名称,并且包含了一组键值对。
  • Environment 接口使用场景

    • ⽤于属性占位符处理。

    • 用于转换 Spring 配置属性类型。

    • 用于存储 Spring 配置属性源(PropertySource)。

    • 用于 Profiles 状态的维护。

  • Environment 的标准实现类图

1.1 spring 环境配置与管理的核心API

  • spring 环境配置与管理的核心API,位于 spring-core 项目中,这些核心API包括但不限于:
    • (abstract class) org.springframework.core.env.PropertySource
      • 表示名称/值属性对的源的抽象基类。底层的{@linkplain #getSource() 源对象}可以是封装属性的任何类型{@code T}。示例包括{@link java.util.Properties}对象,{@link java.util.Map}对象,{@code ServletContext}和{@code ServletConfig}对象(用于访问初始化参数)。探索{@code PropertySource}类型层次结构以查看提供的实现。
    • (interface) org.springframework.core.env.PropertySources extends Iterable<PropertySource<?>>
    • (class) org.springframework.core.env.MutablePropertySources implements PropertySources
    • (Interface) org.springframework.core.env.PropertyResolver
    • (Interface) org.springframework.core.env.Environment extends PropertyResolver
    • (Interface) org.springframework.core.env.ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver
    • (abstract class) org.springframework.core.env.AbstractEnvironment implements ConfigurableEnvironment
    • (class) org.springframework.core.env.StandardEnvironment extends AbstractEnvironment
    • (Interface) org.springframework.core.env.Profiles
    • (final class) org.springframework.core.env.ProfilesParser
    • ...

AbstractEnvironment 的具体实现类

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <!-- 5.2.15.RELEASE / ... -->
    <version>${spring.version}</version>
    <scope>compile</scope>
</dependency>

1.2 env 的 profiles :对不同环境配置进行分组

1.2.1 env 的 profiles 基本使用

首先,我们知道,整个spring应用运行的环境信息:profiles + properties

先看profiles,在代码中的配置为:

spring.profiles.active=prd/test/dev...

profiles作用:对 spring bean 逻辑分组

  • 先定义一个ProfileService类,包含一个私有属性profile,如下:

  • 新建一个ProfileConfiguration配置类,将新建的ProfileService类的实例bean定义在这里,等到springboot项目启动的时候,将可以将ProfileService实例bean装载的ioc容器中去了,整个如下:

  • 在ProfileConfiguraiton配置类的各个bean上,加上@Profile 注解,模拟设置两种不同环境下的bean,声明不同环境的bean

使用 @Profile 对不同的bean逻辑分布 xml 和注解都可以

  • 现在我们不使用SpringbootApplication类启动了,直接写一个main方法启动即可。例如如下,在启动之前,在Environment里面设置profile,setActiveProfile() 参数是数组,可以设置多个。
    • 这里测试之用,先设置“prd”,最后context.getBean可以取出ioc容器中的bean,打印到控制台就好。

一般来说,getBean@Autowired 2种方式都可以取出 spring ioc容器中的 bean.

  • 运行 springboot 工程,如果打印出来的ioc容器中的bean,prd环境中的bean,就可以了。

至此,对于env 的 profiles设置成功。

1.2.2 配置环境信息的方法

  • 我们能够配置环境的地方不止一个,不仅像上面在启动类中可以配置,还可以在 application.properties/yaml 配置文件中配置,还可以在 程序启动参数(args)JVM 参数等。

    • application.* 等本地配置文件

    • 程序启动参数(args)

    • JVM 参数

    • 远程配置中心(nacos) 或 远程第三方服务

    • ...


1.2.3 setActiveProfiles(...) : 入参是可设置多个 profile 的数组

setActiveProfiles() 参数是数组,可以设置多个,如下:

至此,对于 env 的 profiles 的讲解完成。

1.3 env 的 properties:存放环境配置

1.3.1 用 @Value 获取配置

用 @Value 取出普通配置

  • 新建一个UserController类,打印出application.properties配置文件中的env属性即可,如下:

application.properties配置文件中的env属性为“hello world”,配置如下:

  • 运行成功,取出来了,如下:

所以,我们看到,properties存放属性的环境或文件信息。

用 @Value 取出系统配置(如: java.version)

实际上,@Value注解还可以访问到系统环境变量的信息,如java.version,取出当前jdk版本,试一试:

看,打印出来了。

1.3.2 用 Environment 实例 获取配置

用 Environment 实例 bean 取出系统配置(如: java.version)

我们用@Autowired取出spring ioc容器中的Environment实例bean,然后直接用Environment实例bean(不用@Value)来取,如下:
取出spring ioc容器中的实例bean有两种方式,getBean方法 或 @Autowired注解。

看,也打印出来了。

1.3.3 为什么可以用 Environment 实例bean取出java.version这种系统配置?

那问题来了,为什么可以用Environment取出java.version这种系统配置呢?一起来看看Spring中的Environment接口,如下:

理由很简单,因为Environment实例bean已经在ioc容器中了,所以要取出系统配置java.version很简单,因为这个系统配置java.version就是写在Environment的实例bean里面的。

其他的,如BeanFactory、ApplicationContext实例bean也是在ioc容器里的,还实现了Environment接口,如下:

其实,作为Spring容器的内置接口,Environment中的配置有很多来源:系统的环境变量(如上面提到的java.version)、系统变量 System.propreties等。

大体上来说,Environment中的配置来源大体包括两大类:系统属性源 + springboot属性源,如下:

这个图很重要,本文接下来就讲这个图,讲Environment是如何处理 “系统属性源” 和 “springboot属性源” 的。

springboot启动类自动扫描所在包及其子包下spring+springmvc注解,但是不会扫描ibatis的@Mapper注解

1.4 Environment 综合应用

获取方式

  • 在 spring 启动过程已经对 Environment 进行初始化了,那么我们是如何获取/使用该对象的? 我们可以通过:
  • 实现接口 EnvironmentAware
  • 直接 @Autowired

均可以很方便的得到当前应用的环境:Environment 对象。

  • 实现了此接口的类都应该有一个Environment类型 的环境,并且可以通过getEnvironment 方法取得。

我们熟知的所有的Spring应用上下文都实现了这个接口,因为ApplictionContext 就实现了这个接口,表示每个应用上下文都是有自己的运行时环境的。

EnvironmentCapable : 对外提供 Environment ———— ApplicationContext 及其接口实现类

  • 完整源码
package org.springframework.core.env;

public interface EnvironmentCapable {
    Environment getEnvironment();
}
  • 集成、或实现此接口的类

主要是 ApplicationContext 及其接口实现类

EnvironmentAware : 应用程序获取 Environment

在类中通过实现该接口,spring 容器会自动帮我们注入 Environment 对象;这样子我们就可以拿到这个对象进行我们想要的操作。

在 Spring 中,EnvironmentAware 接口是一个回调接口,它提供了一个用于设置 Bean 所在环境的方法。

当一个 Bean 实现了 EnvironmentAware 接口时,在该 Bean 实例被实例化后,Spring 容器会调用 setEnvironment 方法,并将该 Bean 所在的环境作为参数传递进去。

  • 主要作用:

    • EnvironmentAware 主要用于获取加载当前 Bean 的环境(Environment)的引用,使得 Bean 能够在运行时获取到关于自身所在环境的信息。
  • Demo

import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;

@Component
public class GetEnvDemoBean implements EnvironmentAware {
	private Environment environment;
	
    @Override
    public void setEnvironment(Environment environment) {
	    this.environment = environment; //从外部获取到 environment 对象
        System.out.println("【EnvironmentAware】Bean 所在环境是:" + environment);
    }
}

基于 env.resolvePlaceholders(strVal) 解析占位符变量

[interface] StringValueResolver

StringValueResolver 本身,Spring对它的定义为:一个处理字符串的简单策略接口。

// @since 2.5  该接口非常简单,就是个函数式接口~
@FunctionalInterface
public interface StringValueResolver {
    @Nullable
    String resolveStringValue(String strVal);
}

唯一 public 实现类为:EmbeddedValueResolver

[class] EmbeddedValueResolver implements StringValueResolver : 处理占位符

帮助ConfigurableBeanFactory 处理 placeholders 占位符的。
ConfigurableBeanFactory#resolveEmbeddedValue 处理占位符真正干活的,间接的就是它。

// @since 4.3  这个类出现得还是蛮晚的   因为之前都是用内部类的方式实现的,这个实现类是最为强大的  只是 SpEL
public class EmbeddedValueResolver implements StringValueResolver {

    // BeanExpressionResolver  它支持的是SpEL  可以说非常的强大
    // 并且它有 BeanExpressionContext 就能拿到BeanFactory工厂,就能使用它的`resolveEmbeddedValue`来处理占位符
    // 双重功能都有了~~~拥有了和@Value一样的能力,非常强大
    private final BeanExpressionContext exprContext;
    @Nullable
    private final BeanExpressionResolver exprResolver;

    public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
        this.exprContext = new BeanExpressionContext(beanFactory, null);
        this.exprResolver = beanFactory.getBeanExpressionResolver();
    }

    @Override
    @Nullable
    public String resolveStringValue(String strVal) {
        
        // step1 先使用 Bean工厂 处理占位符 resolveEmbeddedValue
        String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal);
        // step2 再使用 el 表达式参与计算
        if (this.exprResolver != null && value != null) {
            Object evaluated = this.exprResolver.evaluate(value, this.exprContext);
            value = (evaluated != null ? evaluated.toString() : null);
        }
        return value;
    }

}

[abstract class] AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory

关于Bean工厂 resolveEmbeddedValue 的实现,我们这里也顺带看看:

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    ...

    @Nullable
    public String resolveEmbeddedValue(@Nullable String value) {
        if (value == null) {
            return null;
        } else {
            String result = value;
            // embeddedValueResolvers 是个复数:因为我们可自定义处理器添加到 bean 工厂来,增强它的能力
            Iterator var3 = this.embeddedValueResolvers.iterator();

            do {
                if (!var3.hasNext()) {
                    return result;
                }

                StringValueResolver resolver = (StringValueResolver) var3.next();
                result = resolver.resolveStringValue(result); //解析字符串的值
            } while(result != null); // 只要处理结果不为 null,所有的处理器都会执行到

            return null;
        }
    }

    ...
}

AbstractApplicationContext extends DefaultResourceLoader

而Bean工厂的处理器都怎么添加进去的呢????

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
        
    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        ...
        // 如果从来没有注册过,Spring容器默认会给注册一个这样的内部类
        // 可以看到,它最终还是委托给了 Environment 去干这件事~~~~~~
        // 显然它最终就是调用 PropertyResolver#resolvePlaceholders
        if (!beanFactory.hasEmbeddedValueResolver()) {
            beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
        }
        ...
    }         
}

由此可见,解析占位符最终都返璞归真 ,真正最终的处理类,处理方法方法是:AbstractPropertyResolver#resolvePlaceholders ,这就是我们非常熟悉了,上面也有详细讲解,最终都是委托给了PropertyPlaceholderHelper 去处理的。

由此可见,若我们通过实现感知接口EmbeddedValueResolverAware 得到一个StringValueResolver 来处理我们的占位符、SpEL计算。

根本原因是:

class ApplicationContextAwareProcessor implements BeanPostProcessor {
    
    // 这里new的一个 EmbeddedValueResolver ,它持有对beanFactory的引用~~~
    // 所以调用者直接使用的是 EmbeddedValueResolver:它支持解析占位符(依赖于Enviroment上面有说到)并且支持SpEL的解析  非常强的
    public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
    }
}

2 Spring Environment 源码解析 I

Environment 接口定义:extends PropertyResolver

// 它继承自 PropertyResolver ,所以是对属性的一个扩展~
public interface Environment extends PropertyResolver {
    
  // 就算被激活  也是支持同时激活多个profiles的~
  // 设置的key是:spring.profiles.active
  String[] getActiveProfiles();
  // 默认的也可以有多个  key为:spring.profiles.default
  String[] getDefaultProfiles();

  // 看看传入的profiles是否是激活的~~~~  支持!表示不激活
  @Deprecated
  boolean acceptsProfiles(String... profiles);
  // Spring5.1后提供的  用于替代上面方法   Profiles是Spring5.1才有的一个函数式接口~
  boolean acceptsProfiles(Profiles profiles);

}

PropertyResolver

  • org.springframework.core.env.PropertyResolver 此接口用于在底层源码 之上解析一系列 的属性值。

例如properties文件,yaml文件,甚至是一些nosql(因为nosql也是k-v形式)。

  • 接口中定义了一系列读取,解析,判断是否包含指定属性的方法:
// @since 3.1  出现得还是相对较晚的  毕竟SpEL也3.0之后才出来嘛
public interface PropertyResolver {

    // 查看规定指定的key是否有对应的value   注意:若对应值是null的话 也是返回false
    boolean containsProperty(String key);
    // 如果没有则返回null
    @Nullable
    String getProperty(String key);
    // 如果没有则返回defaultValue
    String getProperty(String key, String defaultValue);
    // 返回指定key对应的value,会解析成指定类型。如果没有对应值则返回null(而不是抛错~)
    @Nullable
    <T> T getProperty(String key, Class<T> targetType);
    <T> T getProperty(String key, Class<T> targetType, T defaultValue);

    // 若不存在就不是返回null了  而是抛出异常~  所以不用担心返回值是null
    String getRequiredProperty(String key) throws IllegalStateException;
    <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

    // 解析${...}这种类型的占位符,把他们替换为使用getProperty方法返回的结果,解析不了并且没有默认值的占位符会被忽略(原样输出)
    String resolvePlaceholders(String text);
    // 解析不了就抛出异常~
    String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

}
  • 它的继承链,大致如下:
    PropertyResolver ->ConfigurablePropertyResolver ->AbstractPropertyResolver ->PropertySourcesPropertyResolver

ConfigurablePropertyResolver : extends PropertyResolver

顾名思义,它是一个可配置的处理器。这个方法不仅有父接口所有功能,还扩展定义类型转换、属性校验、前缀、后缀、
分隔符 等一些列的功能,这个在具体实现类里有所体现~

public interface ConfigurablePropertyResolver extends PropertyResolver {

    // 返回在解析属性时使用的ConfigurableConversionService。此方法的返回值可被用户定制化set
    // 例如可以移除或者添加Converter  cs.addConverter(new FooConverter());等等
    ConfigurableConversionService getConversionService();
    // 全部替换ConfigurableConversionService的操作(不常用)  一般还是get出来操作它内部的东东
    void setConversionService(ConfigurableConversionService conversionService);

    // 设置占位符的前缀  后缀    默认是${}
    void setPlaceholderPrefix(String placeholderPrefix);
    void setPlaceholderSuffix(String placeholderSuffix);
    // 默认值的分隔符   默认为冒号:
    void setValueSeparator(@Nullable String valueSeparator);
    // 是否忽略解析不了的占位符,默认是false  表示不忽略~~~(解析不了就抛出异常)
    void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);

    /**
     * Specify which properties must be present, to be verified by
     * {@link #validateRequiredProperties()}.
     */
    void setRequiredProperties(String... requiredProperties);
    void validateRequiredProperties() throws MissingRequiredPropertiesException;
}

ConfigurableXXX 成了Spring的一种命名规范,或者说是一种设计模式。它表示可配置的,所以都会提供大量的set方法。

Spring很多接口都是读写分离的,最顶层接口一般都只会提供只读方法,这是Spring框架设计的一般规律之一

AbstractPropertyResolver : implements ConfigurablePropertyResolver

它是对ConfigurablePropertyResolver 的一个抽象实现,实现了了所有的接口方法,并且只提供一个抽象方法给子类去实现。

// @since 3.1
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
    @Nullable
    private volatile ConfigurableConversionService conversionService;

    // PropertyPlaceholderHelper是一个极其独立的类,专门用来解析占位符  我们自己项目中可拿来使用  因为它不依赖于任何其他类
    @Nullable
    private PropertyPlaceholderHelper nonStrictHelper;
    @Nullable
    private PropertyPlaceholderHelper strictHelper;
    
    private boolean ignoreUnresolvableNestedPlaceholders = false;
    private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
    private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
    @Nullable
    private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
    private final Set<String> requiredProperties = new LinkedHashSet<>();

    // 默认值使用的DefaultConversionService
    @Override
    public ConfigurableConversionService getConversionService() {
        // Need to provide an independent DefaultConversionService, not the
        // shared DefaultConversionService used by PropertySourcesPropertyResolver.
        ConfigurableConversionService cs = this.conversionService;
        if (cs == null) {
            synchronized (this) {
                cs = this.conversionService;
                if (cs == null) {
                    cs = new DefaultConversionService();
                    this.conversionService = cs;
                }
            }
        }
        return cs;
    }
    ... // 省略get/set
    @Override
    public void setRequiredProperties(String... requiredProperties) {
        for (String key : requiredProperties) {
            this.requiredProperties.add(key);
        }
    }
    // 校验这些key~
    @Override
    public void validateRequiredProperties() {
        MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
        for (String key : this.requiredProperties) {
            if (this.getProperty(key) == null) {
                ex.addMissingRequiredProperty(key);
            }
        }
        if (!ex.getMissingRequiredProperties().isEmpty()) {
            throw ex;
        }
    }
    ... //get/set property等方法省略  直接看处理占位符的方法即可
    @Override
    public String resolvePlaceholders(String text) {
        if (this.nonStrictHelper == null) {
            this.nonStrictHelper = createPlaceholderHelper(true);
        }
        return doResolvePlaceholders(text, this.nonStrictHelper);
    }
    private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
        return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders);
    }
    // 此处:最终都是委托给PropertyPlaceholderHelper去做  而getPropertyAsRawString是抽象方法  根据key返回一个字符串即可~
    private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
        return helper.replacePlaceholders(text, this::getPropertyAsRawString);
    }
}

最终,处理占位的核心逻辑在
PropertyPlaceholderHelper 身上,这个类不可小觑,是一个与业务无关非常强大的工具类,我们可以直接拿来使用

PropertyPlaceholderHelper

将字符串里的占位符内容,用我们配置的properties里的替换。这个是一个单纯的类,没有继承没有实现,而且简单无依赖,没有依赖Spring框架其他的任何类。

// @since 3.0  Utility class for working with Strings that have placeholder values in them
public class PropertyPlaceholderHelper {

    // 这里保存着  通用的熟悉的 开闭的符号们~~~
    private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);
    static {
        wellKnownSimplePrefixes.put("}", "{");
        wellKnownSimplePrefixes.put("]", "[");
        wellKnownSimplePrefixes.put(")", "(");
    }

    private final String placeholderPrefix;
    private final String placeholderSuffix;
    private final String simplePrefix;
    @Nullable
    private final String valueSeparator;
    private final boolean ignoreUnresolvablePlaceholders; // 是否采用严格模式~~

    // 从properties里取值  若你有就直接从Properties里取值了~~~
    public String replacePlaceholders(String value, final Properties properties)  {
        Assert.notNull(properties, "'properties' must not be null");
        return replacePlaceholders(value, properties::getProperty);
    }

    // @since 4.3.5   抽象类提供这个类型转换的方法~ 需要类型转换的会调用它
    // 显然它是委托给了ConversionService,而这个类在前面文章已经都重点分析过了~
    @Nullable
    protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
        if (targetType == null) {
            return (T) value;
        }
        ConversionService conversionServiceToUse = this.conversionService;
        if (conversionServiceToUse == null) {
            // Avoid initialization of shared DefaultConversionService if
            // no standard type conversion is needed in the first place...
            if (ClassUtils.isAssignableValue(targetType, value)) {
                return (T) value;
            }
            conversionServiceToUse = DefaultConversionService.getSharedInstance();
        }
        return conversionServiceToUse.convert(value, targetType);
    }

    // 这里会使用递归,解析占位符如:${}
    protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { ... }
}

PropertySourcesPropertyResolver

从上面知道
AbstractPropertyResolver 封装了解析占位符的具体实现。
PropertySourcesPropertyResolver 作为它的子类它只需要提供数据源,所以它主要是负责提供数据源。

// @since 3.1    PropertySource:就是我们所说的数据源,它是Spring一个非常重要的概念,比如可以来自Map,来自命令行、来自自定义等等~~~
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {

    // 数据源们~
    @Nullable
    private final PropertySources propertySources;

    // 唯一构造函数:必须制定数据源~
    public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
        this.propertySources = propertySources;
    }

    @Override
    public boolean containsProperty(String key) {
        if (this.propertySources != null) {
            for (PropertySource<?> propertySource : this.propertySources) {
                if (propertySource.containsProperty(key)) {
                    return true;
                }
            }
        }
        return false;
    }
    ...
    //最终依赖的都是propertySource.getProperty(key);方法拿到如果是字符串的话
    //就继续交给 value = resolveNestedPlaceholders((String) value);处理
    @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) {
                    // 若值是字符串,那就处理一下占位符~~~~~~  所以我们看到所有的PropertySource都是支持占位符的
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = resolveNestedPlaceholders((String) value);
                    }
                    logKeyFound(key, propertySource, value);
                    return convertValueIfNecessary(value, targetValueType);
                }
            }
        }
        return null;
    }
    ...
}

ConfigurableEnvironment : extends Environment, ConfigurablePropertyResolver

扩展出了
修改 和配置profiles的一系列方法,包括用户自定义的和系统相关的属性。所有的 环境实现类也都是它的实现~

// @since 3.1
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
    void setActiveProfiles(String... profiles);
    void addActiveProfile(String profile);
    void setDefaultProfiles(String... profiles);

    // 获取到所有的属性源~      MutablePropertySources表示可变的属性源们~~~ 它是一个聚合的  持有List<PropertySource<?>>
    // 这样获取出来后,我们可以add或者remove我们自己自定义的属性源了~
    MutablePropertySources getPropertySources();

    // 系统环境属性~~~
    Map<String, Object> getSystemProperties();
    Map<String, Object> getSystemEnvironment();

    // 合并两个环境配置信息~  此方法唯一实现在AbstractEnvironment上
    void merge(ConfigurableEnvironment parent);
}

AbstractEnvironment : implements ConfigurableEnvironment

该抽象类完成了对active、default等相关方法的复写处理。它内部持有一个
MutablePropertySources 引用来管理属性源。 So,留给子类的活就不多了, 只需要把你的属性源注册给我就OK了

public abstract class AbstractEnvironment implements ConfigurableEnvironment {

    public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
    public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
    public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";

    // 保留的默认的profile值   protected final属性,证明子类可以访问
    protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";


    private final Set<String> activeProfiles = new LinkedHashSet<>();
    // 显然这个里面的值 就是default这个profile了~~~~
    private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());

    // 这个很关键,直接new了一个 MutablePropertySources来管理属性源们
    // 并且是用的PropertySourcesPropertyResolver来处理里面可能的占位符~~~~~
    private final MutablePropertySources propertySources = new MutablePropertySources();
    private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);

    // 唯一构造方法  customizePropertySources是空方法,交由子类去实现,对属性源进行定制~ 
    // Spring对属性配置分出这么多曾经,在SpringBoot中有着极其重要的意义~~~~
    public AbstractEnvironment() {
        customizePropertySources(this.propertySources);
    }
    // 该方法,StandardEnvironment实现类是有复写的~
    protected void customizePropertySources(MutablePropertySources propertySources) {
    }

    // 若你想改变默认default这个值,可以复写此方法~~~~
    protected Set<String> getReservedDefaultProfiles() {
        return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
    }
    
    //  下面开始实现接口的方法们~~~~~~~
    @Override
    public String[] getActiveProfiles() {
        return StringUtils.toStringArray(doGetActiveProfiles());
    }
    protected Set<String> doGetActiveProfiles() {
        synchronized (this.activeProfiles) {
            if (this.activeProfiles.isEmpty()) { 
        
                // 若目前是empty的,那就去获取:spring.profiles.active
                String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
                if (StringUtils.hasText(profiles)) {
                    //支持,分隔表示多个~~~且空格啥的都无所谓
                    setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.activeProfiles;
        }
    }


    @Override
    public void setActiveProfiles(String... profiles) {
        synchronized (this.activeProfiles) {
            this.activeProfiles.clear(); // 因为是set方法  所以情况已存在的吧
            for (String profile : profiles) {
                 // 简单的valid,不为空且不以!打头~~~~~~~~
                validateProfile(profile);
                this.activeProfiles.add(profile);
            }
        }
    }

    // default profiles逻辑类似,也是不能以!打头~
    @Override
    @Deprecated
    public boolean acceptsProfiles(String... profiles) {
        for (String profile : profiles) {

            // 此处:如果该profile以!开头,那就截断出来  把后半段拿出来看看   它是否在active行列里~~~ 
            // 此处稍微注意:若!表示一个相反的逻辑~~~~~请注意比如!dev表示若dev是active的,我反倒是不生效的
            if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
                if (!isProfileActive(profile.substring(1))) {
                    return true;
                }
            } else if (isProfileActive(profile)) {
                return true;
            }
        }
        return false;
    }

    // 采用函数式接口处理  就非常的优雅了~
    @Override
    public boolean acceptsProfiles(Profiles profiles) {
        Assert.notNull(profiles, "Profiles must not be null");
        return profiles.matches(this::isProfileActive);
    }

    // 简答的说要么active包含,要门是default  这个profile就被认为是激活的
    protected boolean isProfileActive(String profile) {
        validateProfile(profile);
        Set<String> currentActiveProfiles = doGetActiveProfiles();
        return (currentActiveProfiles.contains(profile) ||
                (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
    }

    @Override
    public MutablePropertySources getPropertySources() {
        return this.propertySources;
    }

    public Map<String, Object> getSystemProperties() {
        return (Map) System.getProperties();
    }
    public Map<String, Object> getSystemEnvironment() {
        // 这个判断为:return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);
        // 所以我们是可以通过在`spring.properties`这个配置文件里spring.getenv.ignore=false关掉不暴露环境变量的~~~
        if (suppressGetenvAccess()) {
            return Collections.emptyMap();
        }
        return (Map) System.getenv();
    }
// 把父环境的属性合并进来~~~~  
    // 在调用ApplicationContext.setParent方法时,会把父容器的环境合并进来  以保证父容器的属性对子容器都是可见的
    @Override
    public void merge(ConfigurableEnvironment parent) {
        for (PropertySource<?> ps : parent.getPropertySources()) {
            if (!this.propertySources.contains(ps.getName())) {
                this.propertySources.addLast(ps); // 父容器的属性都放在最末尾~~~~
            }
        }
        // 合并active
        String[] parentActiveProfiles = parent.getActiveProfiles();
        if (!ObjectUtils.isEmpty(parentActiveProfiles)) {
            synchronized (this.activeProfiles) {
                for (String profile : parentActiveProfiles) {
                    this.activeProfiles.add(profile);
                }
            }
        }
        // 合并default
        String[] parentDefaultProfiles = parent.getDefaultProfiles();
        if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {
            synchronized (this.defaultProfiles) {
                this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
                for (String profile : parentDefaultProfiles) {
                    this.defaultProfiles.add(profile);
                }
            }
        }
    }

    // 其余方法全部委托给内置的propertyResolver属性,因为它就是个`PropertyResolver`
    ...
}

StandardEnvironment : extends AbstractEnvironment

  • StandardEnvironment,这个是Spring应用在非web容器运行的环境。
public class StandardEnvironment extends AbstractEnvironment {

    // 这两个值定义着  就是在@Value注解要使用它们时的key~~~~~
    /** 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";


    // 注册MapPropertySource和SystemEnvironmentPropertySource
    // SystemEnvironmentPropertySource是MapPropertySource的子类~~~~
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }
}

3 Spring Environment 源码解析 II

先找到Environment接口

3.1 从run()方法开始

找打springboot工程的run方法,如下:

不断ctrl+左键,进入到真正有意义的run方法中,如下:

在这个真正有意义的run方法中,使用prepareEnvironment方法得到一个env实例,然后将这个实例设置到context中去,如下:

源码中,spring或springboot初始化加载时,env 在 context 之前,合乎常理。这里看的springboot的源码,其实spring的源码也一样。

选择prepareEnvironment方法

进入prepareEnvironment方法,先使用getOrCreateEnvironment方法新建或获得一个环境变量,然后将这个env实例放到configureEnvironment方法中去完成相关配置,最后通过listeners.environmentPrepared方法,将env实例交给监听事件,如下:

接下来看一下SpringApplication类prepareEnvironment()方法的三个子方法。

3.2 第一子方法:新建或获取环境getOrCreateEnvironment()方法

可以看到,如果类变量env不为null,就直接返回,如果为null,就根据类变量webApplicationType的实例类型,new一个Env实现类实例返回。

所以说,getOrCreateEnvironment()方法中涉及三个类:StandardEnvironment StandardServletEnvironment StandardReactiveWebEnvironment,这三个类都继承AbstractEnvironment,AbstractEnvironment又继承于Environment接口,关系如下:

先看AbstractEnvironment类

3.2.1 AbstractEnvironment类

在AbstractEnvironment的构造方法中,调用一个自定义属性源的方法,如下:

这个自定义属性源的方法,在AbstractEnvironment中是空实现,只能看它的子类了。

值得注意的是,对于AbstractEnvironment构造函数,

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

这是一个很优美的设计,可以将customizePropertySources的实现交给子类的处理,是模板模式。

AbstractEnvironment类就到这里的,我们看其子类StandardEnvironment类。

3.2.2 StandardEnvironment类

既然AbstractEnvironment类的customizePropertySources()是空实现,看看其子类StandardEnvironment类。

StandardEnvironment类只有一个customizePropertySources方法实现,也看完了,且看StandardServletEnvironment类。

3.2.3 StandardServletEnvironment类

直接转到customizePropertySources方法实现,如下:

关于customizePropertySources()方法,customizePropertySources()接收可变参数源作为输入。

该方法在AbstractEnvironment类中为空实现,在StandardEnvironment类中为将系统配置和系统环境变量放到env中,在StandardServletEnvironment类中为servlet配置属性、servlet上下文属性、JNDI属性,并调用StandardEnvironment的customizePropertySources()方法,所以同时有了servlet配置属性、servlet上下文属性、JNDI属性、系统配置和系统环境变量五种属性。

源码设计的优美之处1:StandardServletEnvironment类中的customizePropertySources()方法调用了StandardEnvironment的customizePropertySources()方法,因为是它里面将系统配置和系统环境变量放到env中;StandardEnvironment的customizePropertySources()方法没有调用AbstractEnvironment的customizePropertySources()方法,因为是它里面将里面方法体为空,不需要调用;这体现了源码的优美。

源码设计优美之处2:

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

AbstractEnvironment类的构造方法中调用customizePropertySources(),以后customizePropertySources()一层层被重写,反正调用最后子类的,具体环境生产具体对象,这体现了源码设计优美。

3.2.4 MutablePropertySources类

customizePropertySources()接收可变参数源作为输入,实际上就是一个MutablePropertySources类对象,让我们来看一下这个MutablePropertySources类,其定义如下:

让我们看看StandardServletEnvironment添加的三个属性是什么

在我们使用spring+springmvc的时候,需要配置一个web.xml,web.xml里面需要配置两个标签< context-params>< /context-params>和< init-params>< /init-params>,当时我们只是这样这样使用,实际上,web.xml中这两个标签作为属性配置到env里面去了。

public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = “servletContextInitParams”; 就是 < context-params>< /context-params>
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = “servletConfigInitParams”; 就是 < init-params>< /init-params>

我们知道,StandardEnvironment:系统变量+系统环境变量

/** System environment property  name: @value. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME  

/** JVM system properties property  name: @value. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME

上面,前者表示系统环境变量,所以我们刚才使用@Value(“${java.version}”)访问到了系统环境变量,后者表示系统属性。

我们现在完善图,一图小结源码结构:

最后一个问题,配置顺序,优先级如何确定?

回答:不管是StandardEnvironment类和还是StandardServletEnvironment类,都是使用addLast添加属性的,所有属性的顺序就是源代码从上到下。

好了,

ConfigurableEnvironment environment = getOrCreateEnvironment(); 这行代码完了,回到SpringApplication类,看configureEnvironment(environment, applicationArguments.getSourceArgs());这行代码。

3.3 第二子方法:准备环境configureEnvironment()

3.3.1 SpringApplication类中的configureEnvironment()

configureEnvironment()是在新建/获取env实例后执行的,如下:

在configureEnvironment()方法中,第一个实参就是ConfigurableEnvironment类对象,就是env对象,刚刚看过了,第二个参数是一个DefaultApplicationArguments类对象.getSourceArgs(),它是在源码中new出来的,如下(简答了解就好,只要知道命令行参数也是可以被识别的就好):

进入到重点,看到configureEnvironment方法,如下:

类变量addConversionService 默认为true,private boolean addConversionService

它表示统一类型转换,当其为true,执行if代码块,得到一个conversionService,并设置到Spring框架的env中去。

接着往下看,可以看到,configureEnvironment方法先后执行了两个方法,就是 configurePropertySources(environment, args); 和 configureProfiles(environment, args); ,就是整个spring应用运行的环境信息:profiles + properties,就是在这两个方法里面解析出来的。

3.3.2 解析properties属性:SpringApplication类的configurePropertySources()

看到configurePropertySources()方法,这个方法解析开发者配置的所有properties属性。

先获得env实例中的属性源,如果默认属性不为空,将其添加到sources中,addLast就是在list末尾添加,如果启动的时候,命令行中也传入了参数,也添加进来,没传入也没关系。

小结,继续更新env图

env中添加命令行参数很好懂,但是默认参数defaultproperties是什么?从哪里来的?先看这个defaultproperties的定义,它在SpringApplication类中,默认是一个map结构,定义如下:

值得注意的是,这个defaultproperties仅仅在springboot才有的,spring是没有的。

关于这个defaultproperties,可以自己设置 setDefautProperties,尝试一下,如下面springboot启动类中。

运行启动起来,看,在源码中断点,真的走到了这个地方,取出defaultProperties变量,看到自己手动添加的属性。

好了,configurePropertySources()方法完成了,下面看configureProfiles()方法。

3.3.3 SpringApplication类的configureProfiles()

先打开configureProfiles()的源码,看到就是根据类变量additionalProfiles新建一个LinkedHashSet,然后将env变量中的profiles属性都放到这个新建的set中,最后将这个set变为字符串又放到env中去。

好了,我们进入到setActiveProfiles()方法,如下:

setActiveProfiles()设置env中的profile

关于configureProfiles方法,看起来好像没什么意义?将env中的profiles放到set中,然后又将set放到env中。

但是,请注意,这里这个AbstractEnvironment类中的setActiveProfiles()方法接收String类型可变数组,同时将字符串类型的profile放到一个Set类型的集合activeProfiles中,因为set集合,可以配置多个,但是不能字符串重名。

实际上,这个方法我们一开始就用到,在env中设置profile,接受一个数组,包含两个字符串 “prd” 和 “env” ,保证既是数组又不重名。

其实,在application.properties文件中设置和在Run/Debug Configurations中设置都是一个道理,底层都是调用这个方法setActiveProfiles()。

3.4 第三子方法:开始加载springboot的配置listeners.environmentPrepared(environment)

3.4.1 listeners.environmentPrepared(environment)

这里就是一个监听逻辑了,复习一下,在Spring中,通用的监听逻辑是:自定义一个事件类,事件发布者发布一个自定义事件,时间接受者listener接收自定义事件及其子类事件。

进入listeners.environmentPrepared(environment);

再进入environmentPrepared方法,如下:

看一下广播事件的具体逻辑,如下:

再进去invokeListener方法,如下:

这个listener.onApplicationContext(event)就是表示监听者监听到事件触发后要完成的相应逻辑。

好了,我们重温一下这个调用关系,如下:

好了,我们来看看这个listener监听到事件发生后的具体操作,进入onApplicationEvent方法,但是这种方法有很多个,如下:

任意找一个,这里找ConfigFileApplicationListener类,可以看到对于通过event类型,执行了不同操作,如下:

对于onApplicationEvent的第二个方法onApplicationPreparedEvent,如下:

EnvironmentPostProcessor有很多实现类,如下:

找到几个EnvironmentPostProcessor的实现类,这里选择SpringApplicationJsonEnvironmentPostProcessor类,这个类中,实现了两个接口,EnvironmentPostProcessor接口是扩展,Ordered接口是排序,如下:

3.4.2 onApplicationEnvironmentPreparedEvent()

关于onApplicationEvent的第一个方法onApplicationEnvironmentPreparedEvent()方法,如下:

再次进入到postProcessEnvironment方法,如下:

好了,整理一下调用关系,如下:

继续进入到addPropertySources方法,如下:

我们注意到,在ConfigFileApplicationListener类中,里面有三个重要的类变量,DEFAULT_PROPERTIES默认属性,DEFAULT_SEARCH_LOCATIONS默认扫描位置(开发者的文件应该存放的路径位置),DEFAULT_NAMES默认名称(开发者的文件的默认名称),如下:

在addPropertySources方法中,三步走,先添加,然后新建一个Loader,最后执行load方法,如下:

第一步,先添加,进入RandomValuePropertySource.addToEnvironment方法,如下:

3.4.3 RandomValuePropertySource类

我们先来认识一下这个RandomValuePropertySource类,类上的注释就告诉我们怎么配置,如下:

类中有两个常量,默认随机属性名称为random,前缀为random.,如下:

getProperty方法:当实参name前缀不为random.,直接返回为null;当实参name的前缀为random.,调用getRandomValue方法返回一个随机值。

getRandomValue方法:根据实参name取前缀长度的字符串匹配,返回具体的随机值,

好了,我们按照RandomValuePropertySource类的配置,自动动手试一试,配置一个name为randomLong,值为random.long,底层是由RandomValuePropertySource类的getRandomValue方法生成的,配置如下:

主代码中通过@Value或者Environment的bean实例取出属性值,这里使用Environment的bean实例,如下:

运行,真的取出来了

刷新一次又变了,每一次都会生成一个随机long,哈哈

好了,玩够了,回到ConfigFileApplicationListener类的addPropertySources方法:添加,新建Loader,执行load方法。

进入到addToEnvironment()方法,这里的addAfter就是在后面添加随机属性。

env如图:

回到ConfigFileApplicationListener类,查看new Loader(environment, resourceLoader).load();

Loader是资源文件加载器

这个Loader类的构造方法就是设置四个类属性,如下:

看到第四句,如下:

this.propertySourceLoaders  SpringFactoriesLoader.
     loadFactoriesPropertySourceLoader.class,getClass.getClassLoader

先看到这个SpringFactoriesLoader.loadFactories方法,这个方法就是加载spring.factories配置文件中的数据,

再看PropertySourceLoader接口本身,这个接口仅包含两个方法,获取文件后缀名方法和加载方法。

看到load方法

进入FilteredPropertySource.apply()方法,如下:

FilteredPropertySource.java

static void apply(ConfigurableEnvironment environment, String propertySourceName, Set<String> filteredProperties,
		Consumer<PropertySource<?>> operation) {
	// 取出env实例bean中的属性源,放到可变属性源propertySources中
	MutablePropertySources propertySources = environment.getPropertySources();  
	// 在可变属性源propertySources中,取得指定propertySourceName
	PropertySource<?> original = propertySources.get(propertySourceName);
	if (original == null) {
		operation.accept(null);  // 如果取出为null,执行operation.accept
		return;
	}
	// 如果取出不为null,即当前存在propertySourceName,新建一个过滤属性源替换当前的propertySourceName
	propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties));
	try {
		operation.accept(original);
	}
	finally {
		propertySources.replace(propertySourceName, original);
	}
}


回到load方法,看到initializeProfiles方法

进入到initializeProfiles方法,该方法初始化profiles属性,逻辑如下:

进入到getOtherActiveProfiles方法,如下:

经历了Loader构造函数,有了Loader对象,看一下load方法,如下:

在进入load方法,如下:

debug执行,如下:

进入到loadForFileExtension方法,如下:




3.X 尾声

Spring Environment全解析,完成了。

Y 推荐文献

X 参考文献

posted @ 2024-10-09 20:58  千千寰宇  阅读(323)  评论(0)    收藏  举报