Spring 源码阅读之 Environment 一 Environment 介绍和占位符解析

概念

environment 翻译过来即是“环境”,表示应用程序运行环境。而在 Spring 应用中,环境包含两个方面的内容:profiles 和 properties.
看看 org.springframework.core.env.Environment 接口的部分注释:

/**
 * Interface representing the environment in which the current application is running.
 * Models two key aspects of the application environment: <em>profiles</em> and
 * <em>properties</em>. Methods related to property access are exposed via the
 * {@link PropertyResolver} superinterface.
 * ... 此处省略N个字...
 */

org.springframework.core.env.Environment 是表示当前应用程序运行环境的接口,为应用程序运行环境的两个关键方面(profiles 和 properties)建模。其中和 property 访问相关的 Methods 在 Environment 父接口 PropertyResolver 中定义。
由上可知 Environment 接口继承自接口 PropertyResolver,PropertyResolver 提供了 propertiy 访问的相关方法,Environment 本身提供了访问 profile 的相关方法。

  • profile 是一个命名的,bean定义的逻辑组。bean 只有在给定的 profile 处于活动状态时,才会在容器中注册
  • propertiy 即属性,可以简单理解为应用程序过程中一直存在的键值对集合

Environment 体系结构

本文基于 web 环境为例阅读 Environment 相关代码,先对 Environment 有个总体了解,看下 Environment 体系的类图:


image.png

由上图可以看到 Environment 体系涉及到类或者接口还是比较多的,下面对其做简单介绍,可以仔细看看并尝试记住,需要对各个类或者接口有简单映像,不然阅读到后面容易迷糊。

  • PropertyResolver:提供 properties 访问功能。
  • ConfigurablePropertyResolver:继承自 PropertyResolver,额外提供 properties 类型转换(基于org.springframework.core.convert.ConversionService)功能,本文不做详细介绍。
  • Environment:继承自 PropertyResolver,额外提供获取 profiles 的功能。
  • ConfigurableEnvironment:继承自 ConfigurablePropertyResolver 和 Environment,提供设置活的 profile 和默认的 profile 的功能。
  • ConfigurableWebEnvironment:继承自 ConfigurableEnvironment,并且提供配置 ServletContext 和ServletConfig 的功能。
  • AbstractEnvironment:实现了 ConfigurableEnvironment 接口,默认属性和存储容器的定义,并且实现了 ConfigurableEnvironment 种的方法,并且为子类预留可覆盖了扩展方法。
  • StandardEnvironment:继承自 AbstractEnvironment,非 Servlet(Web) 环境下的标准 Environment 实现。实现钩子方法,customizePropertySources(..),初始化 systemEnvironment 和 systemProperties。
  • StandardServletEnvironment:继承自 StandardEnvironment,Servlet(Web) 环境下的标准实现。实现钩子方法,customizePropertySources(..),初始化 servletContextInitParams、servletConfigInitParams 和 jndiProperties Web 应用环境需要的参数。

接口方法展示

涉及到的接口提供的方法如下图所示


image.png

properties

PropertySource

先看开始正式介绍 properties 之前,先了解一下 PropertySource 抽象类:

public abstract class PropertySource<T> {
    protected final String name;
    protected final T source;
    
    public boolean containsProperty(String name) {
        return (getProperty(name) != null);
    }
    // 抽象方法,留给字类实现
    @Nullable
    public abstract Object getProperty(String name);

    @Override
    public boolean equals(Object other) {
        return (this == other || (other instanceof PropertySource &&
                ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) other).name)));
    }

    // hashCode方法, 可以看到hash值仅和name有关系,这个 name 有点像 HashMap 里面的 key, 是唯一的
    @Override
    public int hashCode() {
        return ObjectUtils.nullSafeHashCode(this.name);
    }

     // 略。。。
}

源码很简单,预留了一个 getProperty 抽象方法给子类实现,重点需要关注的是覆写了的 equals 和 hashCode 方法,实际上只和 name 属性相关,这一点很重要,说明一个 PropertySource 实例绑定到一个唯一的 name,这个 name 有点像 HashMap 里面的 key。PropertySource 的最常用子类是 MapPropertySource、PropertiesPropertySource、ResourcePropertySource、StubPropertySource 和 ComparisonPropertySource:


image.png
  • MapPropertySource:封装一个 Map<String,Object> 对象为属性源, 继承抽象类 EnumerablePropertySource,增加了一个抽象方法 getPropertyNames()。
  • PropertiesPropertySource:继承自 MapPropertySource,封装一个 Properties 对象为属性源。
  • ResourcePropertySource:继承自 PropertiesPropertySource,封装一个 EncodedResource 对象为属性源。
  • RandomValuePropertySource:封装一个 Random 对象为属性源,位于 spring-boot.jar 中。
  • ServletConfigPropertySource:封装一个 ServletConfig 对象为属性源,位于 spring-web.jar 中。
  • ServletContextPropertySource:封装一个 ServletContext 对象为属性源,位于 spring-web.jar 中。
  • CompositePropertySource: 封装 Set<PropertySource<?>> propertySources 对象作为属性源。
  • CommandLinePropertySource:以命令行参数作为属性源
  • SimpleCommandLinePropertySource:继承自CommandLinePropertySource<CommandLineArgs>。
    简单命令行属性源,使用SimpleCommandLineArgsParser解析器对象解析输入的String数组,把返回的CommandLineArgs对象作为属性的来源。
  • JOptCommandLinePropertySource:基于JOpt Simple的属性源实现,JOpt Simple是一个解析命令行选项参数的第三方库
  • SystemEnvironmentPropertySource:系统环境属性源,继承 MapPropertySource,此属性源在根据name获取对应的value时,与父类实现不太一样。它认为name不区分大小写,且name中包含的'.'点与'_'下划线是等效的,因此在获取value之前,都会对name进行一次处理。
  • StubPropertySource:PropertySource 的一个内部类,source 设置为 new Object(),实际上就是空实现。用于占位。
  • ComparisonPropertySource:继承自 StubPropertySource,所有属性访问方法强制抛出异常,作用就是一个不可访问属性的空实现。

Environment 存储容器

Environment 的静态属性和存储容器都是在 AbstractEnvironment 中定义的 。Environment 的静态属性和存储容器实际上,Environment 的存储容器就是 org.springframework.core.env.PropertySource 的子类集合,AbstractEnvironment 中使用的实例是 org.springframework.core.env.MutablePropertySources。
AbstractEnvironment 中的属性定义:

private final MutablePropertySources propertySources = new MutablePropertySources();

再来看看 MutablePropertySources 是个什么东西:

// PropertySources提供处理多个PropertySource的方法
public interface PropertySources extends Iterable<PropertySource<?>> {
    boolean contains(String name);
    PropertySource<?> get(String name);
}

// MutablePropertySources 实现了PropertySources
public class MutablePropertySources implements PropertySources {
    //内部管理一个 CopyOnWriteArrayList<PropertySource> propertySourceList 对象,即 PropertySource 列表
    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();
    // 判断 propertySourceList 是否包含 name
    @Override
    public boolean contains(String name) {
        return this.propertySourceList.contains(PropertySource.named(name));
    }
     // 从 propertySourceList 获取名称为 name 的 PropertySource, 如果不存在,返回 null, 如果存在一个或多个,返回第一个
    @Override
    public PropertySource<?> get(String name) {
        int index = this.propertySourceList.indexOf(PropertySource.named(name));
        return (index != -1 ? this.propertySourceList.get(index) : null);
    }
    // 迭代器同 propertySourceList 迭代器
    @Override
    public Iterator<PropertySource<?>> iterator() {
        return this.propertySourceList.iterator();
    }

    //添加一个 PropertySource, 赋予最高优先级
    public void addFirst(PropertySource<?> propertySource) {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Adding [%s] PropertySource with highest search precedence",
                    propertySource.getName()));
        }
        removeIfPresent(propertySource);
        this.propertySourceList.add(0, propertySource);
    }

    //添加一个 PropertySource, 赋予最低优先级
    public void addLast(PropertySource<?> propertySource) {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Adding [%s] PropertySource with lowest search precedence",
                    propertySource.getName()));
        }
        removeIfPresent(propertySource);
        this.propertySourceList.add(propertySource);
    }

    //添加一个 PropertySource, 优先级在 relativePropertySourceName 的前一位
    public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Adding [%s] PropertySource with search precedence immediately higher than [%s]",
                    propertySource.getName(), relativePropertySourceName));
        }
        // check 两个 name 是否相同, 如果相同抛出异常
        assertLegalRelativeAddition(relativePropertySourceName, propertySource);
        removeIfPresent(propertySource);
        int index = assertPresentAndGetIndex(relativePropertySourceName);
        addAtIndex(index, propertySource);
    }

    //添加一个 PropertySource, 优先级在 relativePropertySourceName 的后一位
    public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Adding [%s] PropertySource with search precedence immediately lower than [%s]",
                    propertySource.getName(), relativePropertySourceName));
        }
        // check 两个 name 是否相同, 如果相同抛出异常
        assertLegalRelativeAddition(relativePropertySourceName, propertySource);
        removeIfPresent(propertySource);
        int index = assertPresentAndGetIndex(relativePropertySourceName);
        addAtIndex(index + 1, propertySource);
    }

    // 返回 PropertySource 的优先级
    public int precedenceOf(PropertySource<?> propertySource) {
        return this.propertySourceList.indexOf(propertySource);
    }

    // 删除名称为 name 的 PropertySource
    public PropertySource<?> remove(String name) {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Removing [%s] PropertySource", name));
        }
        int index = this.propertySourceList.indexOf(PropertySource.named(name));
        return (index != -1 ? this.propertySourceList.remove(index) : null);
    }

    // 替换名称为 name 的 PropertySource
    public void replace(String name, PropertySource<?> propertySource) {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Replacing [%s] PropertySource with [%s]",
                    name, propertySource.getName()));
        }
        //判断 name 存在, 如果不存在, 抛出异常
        int index = assertPresentAndGetIndex(name);
        this.propertySourceList.set(index, propertySource);
    }

    // 如果  PropertySource 存在,则删除
    protected void removeIfPresent(PropertySource<?> propertySource) {
        this.propertySourceList.remove(propertySource);
    }

    // 将 PropertySource 放置在列表的指定为止(优先级)
    private void addAtIndex(int index, PropertySource<?> propertySource) {
        removeIfPresent(propertySource);
        this.propertySourceList.add(index, propertySource);
    }
}

MutablePropertySources 内部属性是:

private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

这个属性就是最底层的存储容器,也就是环境属性都是存放在一个 CopyOnWriteArrayList<PropertySource<?>> 实例中。MutablePropertySources 是 PropertySources 的子类,它提供了 get(String name)、addFirst、addLast、addBefore、addAfter、remove、replace 等便捷方法,方便操作 propertySourceList 集合的元素。由这些方法的命名可以看出 MutablePropertySources 将 propertySourceList 维护成一个有序的集合,以作为 propertySourceList 获取配置的优先级控制。

小结

  • Spring 应用程序运行环境概念,主要包含两个方面的内容。profiles 决定了哪些 Bean 会被加载;properties 就是配置,就是键值对
  • PropertySource 表示一个属性源(键值对集合)
  • Environment 存储容器 是一个 CopyOnWriteArrayList<PropertySource<?>> 实例,MutablePropertySources 提供了有序访问的 api。

Environment 加载过程

此处关注的是 web 项目,即说明的加载过程使用的是 StandardServletEnvironment。

构造函数配置加载过程

image.png

观察 AbstractEnvironment 构造函数

public AbstractEnvironment() {
    customizePropertySources(this.propertySources);
    if (this.logger.isDebugEnabled()) {
        this.logger.debug(String.format("Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
    }
}
// 空方法,实现类可以自定义 PropertySources
protected void customizePropertySources(MutablePropertySources propertySources) {
}

再看看 StandardEnvironment 的 customizePropertySources()方法和 StandardServletEnvironment 的 customizePropertySources()

// StandardEnvironment 的 customizePropertySources()方法
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    // 构造名称为 systemProperties 的MapPropertySource,即 JVM 变量 PropertySource, 并放入 MutablePropertySources propertySources 中
    propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
    // 构造名称为 systemEnvironment 的MapPropertySource, 即 系统环境变量 PropertySource, 并放入 MutablePropertySources propertySources 中
    propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

// StandardServletEnvironment 的 customizePropertySources()方法
// 此处在 MutablePropertySources 中放入两个 StubPropertySource 类型的 PropertySource,
// servletConfigInitParams 和 servletContextInitParams
// 由上文,我们知道 StubPropertySource 是一个空实现,原因是 Servlet 相关的配置得到 ServletContext 初始化时才有,
// 此处仅起到一个占位的作用, 在 AnnotationConfigEmbeddedWebApplicationContext 初始化时,
// 才会通过 initPropertySources 方重新赋值
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    // 构造名称为 servletConfigInitParams 的 StubPropertySource,并放入 MutablePropertySources 
    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
    // 构造名称为 servletContextInitParams 的 StubPropertySource,并放入 MutablePropertySources 
    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
    // 如果存在 JNDI 环境, 构造名称为 jndiProperties 的 JndiPropertySource,并放入 MutablePropertySources
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
    }
    // 调用父类的customizePropertySources(), 即调用 StandardServletEnvironment 的 customizePropertySources()
    super.customizePropertySources(propertySources);
}

// 被 AnnotationConfigEmbeddedWebApplicationContext 调用,冲洗初始化 servletConfigInitParams 和 servletContextInitParams
@Override
public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
    WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}

// WebApplicationContextUtils#initServletPropertySources
// 重新构建
public static void initServletPropertySources(
            MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) {

    Assert.notNull(propertySources, "'propertySources' must not be null");
    if (servletContext != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) &&
                propertySources.get(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
    // 使用 servletContext 重新构造名称为 servletContextInitParams 的 ServletContextPropertySource,
    // 并替换原有的占位StubPropertySource
    propertySources.replace(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, new ServletContextPropertySource(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext));
    }
    if (servletConfig != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) &&
                propertySources.get(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
    // 使用 servletConfig 重新构造名称为 servletConfigInitParams 的 ServletConfigPropertySource,
    // 并替换原有的占位StubPropertySource
    propertySources.replace(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, new ServletConfigPropertySource(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig));
        }
    }

看完代码,可以简单总结一下代码执行顺序,

  • AbstractEnvironment 的构造方法 调用了 customizePropertySources()。
  • StandardEnvironment 和 StandardServletEnvironment 重写 customizePropertySources()。
  • StandardServletEnvironment 在 customizePropertySources()中构造了 StubPropertySource 类型的 servletConfigInitParams 和 servletContextInitParams 放入 MutablePropertySources 中占位, 当servlet环境准备好, 重新初始化和替换,还构建了 jndiProperties 放入 MutablePropertySources 中。最后调用 StandardEnvironment#customizePropertySources()。
  • StandardEnvironment 在 customizePropertySources 方法中,初始化了 systemProperties 和 systemEnvironment。
    简单总结,此处用来模板方法设计模式:


    image.png

    上面的逻辑都是在 StandardServletEnvironment 构造中完成的,且都是用了 addLast()方法操作,由执行顺序可得,配置的优先级递减排序:

  • servletConfigInitParams
  • servletContextInitParams
  • jndiProperties
  • systemProperties
  • systemEnvironment
    以上构造过程中的配置加载过程已经说完了。再来看看 spring boot web 服务启动过程中的配置加载过程。

命令行参数

本文不过多介绍spring boot web 服务启动流程,直接到启动过程中,环境准备阶段代码。
由上文描述介绍 Environment, 我们知道,Environment 的配置是存储在一个 MutablePropertySources propertySources 对象中,而 ConfigurableEnvironment 暴露了获取该对象的方法

MutablePropertySources getPropertySources();

这就允许程序在任何地方,只要能获取到 Environment 对象,就可以获取到 MutablePropertySources propertySources 对象,并对齐做修改。
此处介绍两个spring boot web 程序启动过程中的两个 case, 学习一下如何修改,主要是修改的地方太多了,没法枚举。

SpringApplication#prepareEnvironment() 命令行参数 commandLineArgs

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {
    // 构造函数创建 Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    //  配置 Environment 的 PropertySources 和 Profiles
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 发布 ApplicationEnvironmentPreparedEvent 事件, 此处不介绍 spring 事件机制, 
    // 事件发布后,其实也有很多地方修改该了Environment配置,此处不做介绍
    listeners.environmentPrepared(environment);
    if (!this.webEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertToStandardEnvironmentIfNecessary(environment);
    }
    return environment;
}

// 配置 Environment 的 PropertySources 和 Profiles
protected void configureEnvironment(ConfigurableEnvironment environment,
        String[] args) {
    // 配置 PropertySources
    configurePropertySources(environment, args);
    // 配置 Profiles
    configureProfiles(environment, args);
}

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
    }
    // 如果 存在命令行参数
    if (this.addCommandLineProperties && args.length > 0) {
        String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
        // MutablePropertySources 已存在 commandLineArgs, 
        // 则将已有的 commandLineArgs 与 新生成的 SimpleCommandLinePropertySource 变成一个合成的 CompositePropertySource, 
        // 替换MutablePropertySources中的 commandLineArgs
        if (sources.contains(name)) {
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(new SimpleCommandLinePropertySource(name + "-" + args.hashCode(), args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        }
        // MutablePropertySources 不存在 commandLineArgs, 
        // 新生成 SimpleCommandLinePropertySource 放入MutablePropertySources的最高优先级位置
        else {
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }
}

构造完成后的Environment debug截图:


image.png

执行configureEnvironment()之后, 可以看到,多了一个的commandLineArgs, Environment debug截图:


image.png

spring-cloud-config 加载外部配置

本文不会详细介绍 spring-cloud-config, 只会介绍其中一段关于 Environment 配置变更的部分。涉及到spring 初始化机制,本文也不会详细介绍。

// 省略不相关代码
// 实现了 ApplicationContextInitializer 接口, 在spring boot启动过程中,会调用 initialize()方法,此处不做详细介绍
@Configuration
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    // 配置加载器, 主要作用是: 通过http请求,去config-server拉取配置
    @Autowired(required = false)
    private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 创建名称为 bootstrapProperties 合成资源
        CompositePropertySource composite = new CompositePropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME);
        // 对配置加载器排序
        AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
        boolean empty = true;
        // 当前应用上下文的 Environment
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        // 遍历多个配置加载器,加载配置
        for (PropertySourceLocator locator : this.propertySourceLocators) {
            //配置加载器返回结果, 如果为空, 继续下次循环, 如果配置不为空,添加到合成资源 CompositePropertySource 中
            PropertySource<?> source = null;
            source = locator.locate(environment);
            if (source == null) {
                continue;
            }
            logger.info("Located property source: " + source);
            composite.addPropertySource(source);
            empty = false;
        }
        // 配置加载器返回的配置非空,即 CompositePropertySource 非空
        if (!empty) {
            MutablePropertySources propertySources = environment.getPropertySources();
            String logConfig = environment.resolvePlaceholders("${logging.config:}");
            LogFile logFile = LogFile.get(environment);
            // 如果 Environment 配置中已经包含 bootstrapProperties, 删除
            if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
                propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
            }
            // 将 CompositePropertySource 添加到 MutablePropertySources 中
            insertPropertySources(propertySources, composite);
            reinitializeLoggingSystem(environment, logConfig, logFile);
            setLogLevels(environment);
            handleIncludedProfiles(environment);
        }
    }
    // 将 CompositePropertySource 添加到 MutablePropertySources 中
    private void insertPropertySources(MutablePropertySources propertySources, CompositePropertySource composite) {
        MutablePropertySources incoming = new MutablePropertySources();
        incoming.addFirst(composite);
        // 构造 PropertySourceBootstrapProperties remoteProperties, 使用 RelaxedDataBinder 为 remoteProperties 赋值
        PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
        new RelaxedDataBinder(remoteProperties, "spring.cloud.config").bind(new PropertySourcesPropertyValues(incoming));
        // 如果 配置为远程覆盖本地,则将 composite 放在 propertySources 最高优先级位置
        if (!remoteProperties.isAllowOverride() 
                  || (!remoteProperties.isOverrideNone() && remoteProperties.isOverrideSystemProperties())) {
            propertySources.addFirst(composite);
            return;
        }
        // 如果 配置为远程不覆盖本地, 则将 composite 放在 propertySources 最低优先级位置
        if (remoteProperties.isOverrideNone()) {
            propertySources.addLast(composite);
            return;
        }
        // 当 systemEnvironment 存在,
        if (propertySources.contains(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
            //  如果配置 overrideSystemProperties 为 false, 将 composite 放在 systemEnvironment 优先级后面
            // 否则, 放在 systemEnvironment 优先级前面
            if (!remoteProperties.isOverrideSystemProperties()) {
                propertySources.addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
                        composite);
            } else {
                propertySources.addBefore(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
                        composite);
            }
        // 当 systemEnvironment 不存在,  则将 composite 放在 propertySources 最低优先级位置
        } else {
            propertySources.addLast(composite);
        }
    }
}

以上内容说明了 命令行参数 和 spring-cloud-config 获取到的远程配置 加载到 Environment 中的过程。

Environment 属性访问

上文提到, Environment 的配置都存储在 MutablePropertySources propertySources 中,那 Environment 是如何访问到配置的呢?
代码在 AbstractEnvironment 中实现:

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

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

@Override
public <T> T getProperty(String key, Class<T> targetType) {
    return this.propertyResolver.getProperty(key, targetType);
}

@Override
public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
    return this.propertyResolver.getProperty(key, targetType, defaultValue);
}

可以看到, 属性访问都委托给了 propertyResolver 对象, 而 PropertySourcesPropertyResolver 的构造函数 需要传入一个 propertySources

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

public PropertySourcesPropertyResolver(PropertySources propertySources) {
    this.propertySources = propertySources;
}

先看看 的类型结构


image.png
  • PropertyResolver 决定基础 source 的接口
  • ConfigurablePropertyResolver 继承 PropertyResolver, 配置接口,用来定制 ConversionService
  • AbstractPropertyResolver 是 ConfigurablePropertyResolver 抽象实现, 决定基础 source 的抽象类
  • PropertySourcesPropertyResolver 继承 AbstractPropertyResolver

PropertyResolver-占位符处理

PropertyResolver是 PropertySourcesPropertyResolver 的顶层接口,主要提供属性检索和解析带占位符的文本。

public interface PropertyResolver {
    boolean containsProperty(String key);
    String getProperty(String key);
    String getProperty(String key, String defaultValue);
    <T> T getProperty(String key, Class<T> targetType);
    <T> T getProperty(String key, Class<T> targetType, T defaultValue);
    @Deprecated
    <T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
    String getRequiredProperty(String key) throws IllegalStateException;
    <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
    String resolvePlaceholders(String text);
    String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}

ConfigurablePropertyResolver 确定解析占位符的一些配置方法。

public interface ConfigurablePropertyResolver extends PropertyResolver {
    ConfigurableConversionService getConversionService();
    void setConversionService(ConfigurableConversionService conversionService);
    void setPlaceholderPrefix(String placeholderPrefix);
    void setPlaceholderSuffix(String placeholderSuffix);
    void setValueSeparator(String valueSeparator);
    void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
    void setRequiredProperties(String... requiredProperties);
    void validateRequiredProperties() throws MissingRequiredPropertiesException;
}

AbstractPropertyResolver 封装了解析占位符的具体实现,而 PropertySourcesPropertyResolver 主要是负责提供数据源。AbstractPropertyResolver 中有两个成员变量都是 PropertyPlaceholderHelper 对象,区别在于构造参数不同。

// 解析占位符,忽略不能解析的占位符
public String resolvePlaceholders(String text) {
    if (this.nonStrictHelper == null) {
        this.nonStrictHelper = createPlaceholderHelper(true);
    }
    return doResolvePlaceholders(text, this.nonStrictHelper);
}

// 解析占位符,不忽略不能解析的占位符, 遇到不能解析的占位符, 抛出异常
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
        this.strictHelper = createPlaceholderHelper(false);
    }
    return doResolvePlaceholders(text, this.strictHelper);
}

// 将解析占位符的逻辑委托给 PropertyPlaceholderHelper helper对象
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
    return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
        // 入参是占位符, 出参是 PropertySource 存储的原生value, 依赖抽象方法 getPropertyAsRawString()
        @Override
        public String resolvePlaceholder(String placeholderName) {
            return getPropertyAsRawString(placeholderName);
        }
    });
}

// 获取 PropertySource 存储的原生value, 由字类实现
protected abstract String getPropertyAsRawString(String key);

在看看PropertyPlaceholderHelper代码:

// 占位符处理器
public class PropertyPlaceholderHelper {
    private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);
    static {
        wellKnownSimplePrefixes.put("}", "{");
        wellKnownSimplePrefixes.put("]", "[");
        wellKnownSimplePrefixes.put(")", "(");
    }


    private final String placeholderPrefix; // 默认 "${"
    private final String placeholderSuffix; // 默认 "}"
    private final String simplePrefix;         
    private final String valueSeparator;    // 默认 ":"
    private final boolean ignoreUnresolvablePlaceholders;  // 是否忽略占位符中不存在的数据源,false会抛出IllegalArgumentException。

     // 构造函数
    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
        Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
        Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");

        this.placeholderPrefix = placeholderPrefix;
        this.placeholderSuffix = placeholderSuffix;
        String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
        if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
            this.simplePrefix = simplePrefixForSuffix;
        }
        else {
            this.simplePrefix = this.placeholderPrefix;
        }
        this.valueSeparator = valueSeparator;
        this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
    }


    // 用来将 value 中占位符替换为从 Properties 取得的值
    public String replacePlaceholders(String value, final Properties properties) {
        Assert.notNull(properties, "'properties' must not be null");
        return replacePlaceholders(value, new PlaceholderResolver() {
            @Override
            public String resolvePlaceholder(String placeholderName) {
                return properties.getProperty(placeholderName);
            }
        });
    }

    // 用来将 value 中占位符替换为从 PlaceholderResolver 取得的值
    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
        Assert.notNull(value, "'value' must not be null");
        return parseStringValue(value, placeholderResolver, new HashSet<String>());
    }
     
    //  用来将 value 中占位符替换为从 PlaceholderResolver 取得的值, 具体实现逻辑
    // 这是一个递归的解析过程,遇到${开头就会查找最后一个}符号,
    // 将最外层占位符内的内容作为新的 value 再次传入 parseStringValue() 方法中,
    // 这样最深层次也就是最先返回的就是最里层的占位符名字。
    // 调用placeholderResolver将占位符名字转换成它代表的值。
    // 如果值为null,则考虑使用默认值 ( valueSeparator 后的内容) 赋值给 propVal。
    // 由于 placeholderResolver 转换过的值有可能还会包含占位符,
    // 所以在此调用 parseStringValue() 方法将带有占位符的 propVal 传入返回真正的值,用 propVal 替换占位符。
    // 如果 propVal==null,判断是否允许忽略不能解析的占位符,
    // 如果可以,重置 startIndex,继续解析同一层次的占位符。否则抛出异常,这个函数的返回值就是它上一层次的占位符解析值
    protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

        StringBuilder result = new StringBuilder(value);

        int startIndex = value.indexOf(this.placeholderPrefix);
        // 当 包含 placeholderPrefix(“${”)时
        while (startIndex != -1) {
            int endIndex = findPlaceholderEndIndex(result, startIndex);
            if (endIndex != -1) {
                // 解析到占位符
                String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                String originalPlaceholder = placeholder;
                // 将解析到的占位符添加到visitedPlaceholders, 如果添加失败,表示由循环的占位符,抛出异常
                if (!visitedPlaceholders.add(originalPlaceholder)) {
                    throw new IllegalArgumentException("Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                }
                // 递归调用, 继续转化占位符, 因为转化到的接口,可能仍然包含占位符
                placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                // 获取占位符里面的 key 对应的值
                String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                // 如果 propVal 为空且 this.valueSeparator 不为空
                if (propVal == null && this.valueSeparator != null) {
                                        int separatorIndex = placeholder.indexOf(this.valueSeparator);       
                    // 如果占位符包含默认值分割符   
                    if (separatorIndex != -1) {
                        // 获取正真的占位符, separatorIndex 之前的部分
                        String actualPlaceholder = placeholder.substring(0, separatorIndex);
                        // 默认值, separatorIndex 之后的部分
                        String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                        // 重新解析正真的占位符, 如果值还是不存在,则使用默认值
                        propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                        if (propVal == null) {
                            propVal = defaultValue;
                        }
                    }
                }
                // 如果 propVal 不为空
                if (propVal != null) {
                    // 递归调用, 继续解析 获取到的占位符值,即 propVal
                    propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                    // 解析到结果 替换结果中
                    result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Resolved placeholder '" + placeholder + "'");
                    }
                    // 找到下一个 “${” 开始的位置, 赋值给 startIndex
                    startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                } else if (this.ignoreUnresolvablePlaceholders) {
                    // 如果解析不到结果, ignoreUnresolvablePlaceholders == true的情况下
                    // 找到下一个 “${” 开始的位置, 赋值给 startIndex 
                    startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                } else {
                    // 如果解析不到结果, ignoreUnresolvablePlaceholders == false 的情况下, 抛出异常
                    throw new IllegalArgumentException("Could not resolve placeholder '" +
                            placeholder + "'" + " in value \"" + value + "\"");
                }
                // 移除解析成功的占位符
                visitedPlaceholders.remove(originalPlaceholder);
            } else {
                // 占位符中 不包含“${”, 结束循环
                startIndex = -1;
            }
        }

        return result.toString();
    }
     // 判断嵌套的占位符是依据simplePrefix,找到当前占位符前缀对应的尾缀 
    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
        int index = startIndex + this.placeholderPrefix.length();
        int withinNestedPlaceholder = 0;
        while (index < buf.length()) {
            if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
                if (withinNestedPlaceholder > 0) {
                    withinNestedPlaceholder--;
                    index = index + this.placeholderSuffix.length();
                }
                else {
                    return index;
                }
            }
            else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
                withinNestedPlaceholder++;
                index = index + this.simplePrefix.length();
            }
            else {
                index++;
            }
        }
        return -1;
    }


    // PlaceholderResolver是一个接口定了一个方法,入参为占位符参数名,出参为占位符代表的值。
    public interface PlaceholderResolver {
        String resolvePlaceholder(String placeholderName);
    }
}

再来看看 PropertySourcesPropertyResolver 代码

public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
    // 数据源
    private final PropertySources propertySources;

    // 获取指定类型获取配置
    // key
    // targetValueType 目标类型
    // resolveNestedPlaceholders 是否处理占位符
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        if (this.propertySources != null) {
            // 顺序遍历 propertySources, 体现了优先级, 之前文中也提到了 MutablePropertySources 维护的是有序列表
            for (PropertySource<?> propertySource : this.propertySources) {
                if (logger.isTraceEnabled()) {
                    logger.trace(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
                }
                // 原生的配置 value
                Object value = propertySource.getProperty(key);
                if (value != null) {
                    // 判断是否处理占位符
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = resolveNestedPlaceholders((String) value);
                    }
                    logKeyFound(key, propertySource, value);
                    // 类型转换,转换成目标类型 targetValueType
                    return convertValueIfNecessary(value, targetValueType);
                }
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Could not find key '%s' in any property source", key));
        }
        return null;
    }
}

ConversionService-类型转换

上文提到,在占位符处理完成之后, 最后一步是 convertValueIfNecessary(), 类型转化,代码还是在 AbstractPropertyResolver 中:

protected <T> T convertValueIfNecessary(Object value, Class<T> targetType) {
    if (targetType == null) {
        return (T) value;
    }
    // 如果自定义 ConversionService, 则使用 DefaultConversionService
    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);
}
关于类型转换部分内容在下一篇文章中介绍

参考:
[https://blog.csdn.net/shenchaohao12321/article/details/80390457](https://blog.csdn.net/shenchaohao12321/article/details/80390457)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352