SpringBoot源码分析(3)--Environment简介/prepareEnvironment准备环境(万字图文源码debug分析)

一、前言

本文基于spring-boot-2.2.14.BUILD-SNAPSHOT源码分析prepareEnvironment这一步骤,prepareEnvironment主要是解析上下文中的所有配置文件,等待后续步骤将值绑定到spring上下文中。

二、Environment

运用 Environment 来获取环境变量值非常简单,只要注入Environment类调用其办法getProperty(key)即可,但知其然知其所以然,简略了解下它的原理,由于后续的几种获取配置的办法都和它休戚相关。

@Slf4j
@SpringBootTest
public class EnvironmentTest {
    @Resource
    private Environment env;
    @Test
    public void Test() {
        String port= env.getProperty("server.port");
        log.info("Environment 端口号 {}", port);
    }
}

之所以写这篇文章,是受jasypt组件的启发。很好奇这玩意儿究竟是如何实现对敏感属性加解密的;现在来看,要想实现这个功能,不仅需要熟悉 Bean 的生命周期、IoC 容器拓展点 (IoC Container Extension Points) 和 Spring Boot 的启动流程等知识,还需要掌握 Environment。这样以后我们就可以自己在配置文件中添加一种加解密方式了。

jasypt的使用只需在pom.xml文件中添加依赖即可

<!-- jasypt加密 如配置文件中ENC() -->
<dependency>
	<groupId>com.github.ulisesbocchio</groupId>
	<artifactId>jasypt-spring-boot-starter</artifactId>
	<version>2.1.0</version>
</dependency>
jasypt.encryptor.password=p@SSwd123

spring.datasource.url=jdbc:mysql://HOST:PORT/db_sql_boy?characterEncoding=UTF-8
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.username=root
spring.datasource.hikari.password=ENC(2wa+DEsldkjdslkdj329xhmwNOHewWLIfquAXaUiqlsSkjierjl82sdlLKKEnml)

2.1、什么是 Environment?

Environment 是 springboot 核心的环境配置接口,它提供了简单的方法来访问应用程序属性,包括系统属性、操作系统环境变量、命令行参数、和应用程序配置文件中定义的属性等等。

Environment是Spring3.1才提供的一个接口。它是对当前运行的应用程序的环境的抽象,下面我们了解一下它的组成。

2.2、Environment组成

Environment接口内容如下所示:

public interface Environment extends PropertyResolver {
    String[] getActiveProfiles();
    String[] getDefaultProfiles();
    boolean acceptsProfiles(Profiles profiles);
}

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);
    String resolvePlaceholders(String text);
}

Environment由两部分组成

1)profiles

profile中文直译是"概述"、“简介”、"轮廓"的意思,但在使用spring开发应用程序的时候,我们对profile的认识更亲切的是用在划分多环境的时候。

通常,我们会将profile划分成如:开发、测试、预生产、生产环境。每个环境会有有些bean不同、配置不同等。每个profile将有相应的bean和配置与之匹配,那么当我们切换profile的时候自然也就切换了相应的bean和配置文件,从而达到在不同环境中快速切换避免不断修改的问题。

这也就是spring的java doc里面描述的"logical group"的意思。

2)properties

properties的概念想必我们已经非常熟悉了,在java中properties代表着key-value的键值对象集合。Environment内部设计了key-value结构的对象来存储相应的键值。

综上所述,Environment中包含着用于切换环境的profile,还包含着存储键值对的properties。

UML类关系图如下:
在这里插入图片描述

在这里插入图片描述
上面的内容中,我们了解了Environment的组成部分包括profile和properties。spring在对Environment进行设计的时候也把这两个部分进行了隔离。

如上图所示,PropertyResolver包含了properties相关的操作,如:getProperty(String key),Environment继承于PropertyResolver同时也就将properties的相关能力给组合了进来。

Environment的则包含了profile的相关操作,如:getActiveProfiles()。

如果查看PropertyResolver和Environment接口的方法,我们就会发现这两个接口都只是包含了如getter方法的获取操作,并没有setter样子的操作。这或许也意味着spring希望在程序的开发运行过程中,Environment尽量是维持稳定的,而不是不断地被修改、变化。

那么在程序启动过程中势必要对Environment进行配置,因此我们会看到多个继承自Environment和PropertyResolver接口地子接口,如:ConfigurableEnvironment和ConfigurablePropertyResolver。

再往下看,AbstractEnvironment显然包含了Environment设计地大部分实现,而从StandardEnvironment再往下走了两个分支,也就是针对reactive和Servlet的Environment实现。

到这里,我们基本了解了Environment主要的相关接口设计,设计路线也比较简单。

profile和properties的数据结构
前面的两个部分,我们了解了Environment包含profile和properties。也知道了Environment相关接口也主要是根据profile和properties来设计的。但是我们并不知道具体的实现里面profile和properties的数据结构是怎么样的。

2.2.1、profile数据结构

从uml类图中,我们清晰地看到Environment的具体实现是在AbstractEnvironment这个抽象类中。AbstractEnvironment类中包含着profile的成员变量

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    private final Set<String> activeProfiles = new LinkedHashSet<>();

	private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
}

profile的存储结构看起来相对简单,就是两个set集合,每个profile就是单纯的一个String类型的字符串表示而已。

activeProfiles表示的是当前应用中"激活"的profile集合,比如我当profile=dev的时候表示当前环境是开发环境。

而defaultProfiles则表示的是默认的profile集合,也就是说如果没有任何指定的profile,那么就会采用默认的。

2.2.2.1、profile示例

在项目开发的过程中,我们难免会遇到开发(dev)、测试(sit)、生产(prod)等环境的切换,而各个环境的配置肯定是不同的。所谓的profile就是用来切换环境的。
在这里插入图片描述

springboot提供了spring.profiles.active属性指定激活哪个文件。然后spring会加载application-{profile}.propertites/yml文件。 多个profile文件用逗号分隔,属性加载时后面的优先级更高,即读取属性时会先从后面的profile中读取属性。

2.2.2、properties数据结构

我们再看看AbstractEnvironment中properties的数据结构

private final MutablePropertySources propertySources = new MutablePropertySources();

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

前面我们一直提到,properties是一种key-value的键值对存储的集合。那么也就是说MutablePropertySources这个类实现了这个概念。

我们先看看MutablePropertySources的继承结构是怎么样的
在这里插入图片描述

Iterable接口表明MutablePropertySources像集合一样是可以迭代的,我们可以大胆猜测其内部就是组成了一个集合。Iterable往下,就是PropertySources,这个接口表示的是PropertySource类的集合,也就是说被迭代的元素就是PropertySource。MutablePropertySources则直接继承于PropertySources。

那么,我们基本可以想得到PropertySource这个类就是properties概念得设计,是我们主要得关注对象。

现在让我们打开MutablePropertySources看看PropertySource的具体结构

public class MutablePropertySources implements PropertySources {

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

    //......省略其他代码
}

跟我们想象得差不多,就是一个PropertySource类的集合作为成员组合在MutablePropertySources中。

我们继续跟进PropertySource这个类,看一下它的源码

public abstract class PropertySource<T> {

	protected final String name;// 属性源名称

	protected final T source;  // 属性源值(一个泛型,比如Map,Property)

    public boolean containsProperty(String name) {//是否包含某个属性
		return (getProperty(name) != null);
	}

    public abstract Object getProperty(String name);//获得属性对应的值

	//......
}

这里的name指的是配置文件的名称,source是一个Map集合或者Property等

如我们创建了一个application.properties文件,内容如

server.port= 8083
spring.profiles.active=prod

name=tom

那么当application.properties这个文件被加载到内存中,并作为一个PropertySource存在的时候,name=application.propertites。也就是说,加载application.properties这样的资源,泛型T将会是一个Map集合,而Map集合包含着application.properties文件中所有的键值对。

proertySourceList保存的数据如下图:(我们看到从上到下有系统配置,环境变量配置,application配置)。proertySourceList存储的对象是PropertySource。PropertySource中name即为文件名或某一特定的配置标识,source就是该文件对应的key/value在这里插入图片描述
PropertySource是一个抽象类。spring将会针对资源的不同来源而使用不同的实现
在这里插入图片描述

  • MapPropertySource : Map 键值对的对象转换为 PropertySource 对象的适配器;
  • PropertiesPropertySource : Properties 对象中的所有配置属性转换为 Spring 环境中的属性值;
  • ResourcePropertySource : 从文件系统或者 classpath 中加载配置属性,封装成 PropertySource对象;
  • ServletConfigPropertySource : Servlet 配置中读取配置属性,封装成 PropertySource 对象;
  • ServletContextPropertySource : Servlet 上下文中读取配置属性,封装成 PropertySource 对象;
  • StubPropertySource : 是个空的实现类,它的作用仅仅是给 CompositePropertySource 类作为默认的父级属性源,以避免空指针异常;
  • CompositePropertySource : 是个复合型的实现类,内部维护了 PropertySource集合队列,可以将多个 PropertySource 对象合并;
  • SystemEnvironmentPropertySource : 操作系统环境变量中读取配置属性,封装成 PropertySource 对象;

三、prepareEnvironment

这一步的主要作用按顺序加载命令行参数, 系统参数和外部配置文件, 创建并配置Web环境, 获取profiles.active属性, 并发布ApplicationEnvironmentPreparedEvent事件, 之后获取属性时, 按顺序获取, 获取到就立即返回, 实现了属性之间的合理加载与替换

  • 根据应用类型创建应用环境:如得到系统的参数、JVM 及 Servlet 等参数,等
  • 将 defaultProperties、commandLine 及 active-prifiles 属性加载到环境中
  • 将配置文件加载到环境中
    在这里插入图片描述
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
													   ApplicationArguments applicationArguments) {
	// 获取或者创建环境   创建就会读取: java环境变量和系统环境变量
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	//配置环境  将命令行参数读取环境变量中
	//将 defaultProperties、commandLine及active-prifiles 属性加载到环境中
    //commandLine 在 args 中配置
    //其它参数可在如下4个路径中配置:servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// 将@PropertieSource的配置信息 放在第一位,它的优先级是最低的
	ConfigurationPropertySources.attach(environment);
	//发布环境已准备事件,这是第二次发布事件,发布了ApplicationEnvironmentPreparedEvent 的监听器 读取了全局配置文件
	listeners.environmentPrepared(environment);
	// 将所有spring.main 开头的配置信息绑定到SpringApplication中
	bindToSpringApplication(environment);
	//如果不是web应用,就转成其他环境如StandardEnvironment
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	//绑定环境
	ConfigurationPropertySources.attach(environment);
	return environment;
}

以下问题将在本文中找到答案:

  1. ConfigurationPropertySources.attach为什么调用两次?

3.1、获取或者创建环境getOrCreateEnvironment

源码如下:

private ConfigurableEnvironment getOrCreateEnvironment() {
	//此处可以设置自定义environment,此处是一个拓展点
	if (this.environment != null) {
		return this.environment;
	}
	switch (this.webApplicationType) {
		case SERVLET:
			//spring boot应用创建的是StandardServletEnvironment
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
	}
}

基于《SpringBoot源码分析(2)–SpringBoot启动源码(万字图文源码debug讲解springboot启动原理)》一文中的分析,我们的项目类型为SERVLET,所以此处会创建StandardServletEnvironment对象。

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
	//......省略其他代码
}

我们可以看一下StandardServletEnvironment的继承依赖关系。实例化StandardServletEnvironment必定会实例化其所有父类, 所以我们首先分析抽象父类AbstractEnvironment的代码, 然后分析StandardEnvironment代码, 最后分析StandardServletEnvironment
在这里插入图片描述

3.1.1、AbstractEnvironment

AbstractEnvironment定义了子类需要实现的类, 并通过模板方法, 在构造函数中, 调用子类的customizePropertySources()方法, 将环境配置全部放入this.propertySources中,

AbstractEnvironment实现了getActiveProfiles和setActiveProfiles方法, 分别用来获取和设置spring.profiles.active属性的配置

//抽象环境类
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    
    //配置是否允许获取SystemEnvironment的配置
    public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";

    //spring启动profile
    public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";

    //spring默认profile
    public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";

    //默认的profile名称
    protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
    
    //用来维护属性列表
    private final MutablePropertySources propertySources = new MutablePropertySources();
 
    //构造函数
    //调用了customizePropertySources方法
    //customizePropertySources方法由子类实现
    public AbstractEnvironment() {
        customizePropertySources(this.propertySources);
    }
    
    //空方法,加载自定义配置文件,如果没有自定义,我们实例化的是StandardServletEnvironment。此处是一个拓展点
    //鼓励子类继承实现
    protected void customizePropertySources(MutablePropertySources propertySources) {}
    
    //获取系统属性
   public Map<String, Object> getSystemEnvironment() {
        if (suppressGetenvAccess()) {
            //如果spring.getenv.ignore配置为true,
            //那么返回空map
            return Collections.emptyMap();
        }
        try {
            //返回系统属性
            return (Map) System.getenv();
        }
        catch (AccessControlException ex) {
            //...
        }
    }
    
    /**
     * 获取activeProfiles
     */
    @Override
    public String[] getActiveProfiles() {
        return StringUtils.toStringArray(doGetActiveProfiles());
    }

    /**
     * 获取activeProfiles
     */
    protected Set<String> doGetActiveProfiles() {
        //上锁
        synchronized (this.activeProfiles) {
            if (this.activeProfiles.isEmpty()) {
                //如果activeProfiles为空
                //那么从propertySources中获取spring.profiles.active属性的值
                //并且用","分割为数组
                String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
                if (StringUtils.hasText(profiles)) {
                    //profile保存到activeProfiles成员变量中
                    setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.activeProfiles;
        }
    }

    @Override
    public void setActiveProfiles(String... profiles) {
        Assert.notNull(profiles, "Profile array must not be null");
        if (logger.isDebugEnabled()) {
            logger.debug("Activating profiles " + Arrays.asList(profiles));
        }
        //上锁
        synchronized (this.activeProfiles) {
            //清空activeProfiles
            this.activeProfiles.clear();
            for (String profile : profiles) {
                validateProfile(profile);
                //重新添加到activeProfiles变量中
                this.activeProfiles.add(profile);
            }
        }
    }
}

3.1.2、StandardEnvironment

StandardEnvironment继承了AbstractEnvironment, customizePropertySources代码执行步骤有:

  • 先添加数据systemProperties(系统属性)到父类的propertySource末尾
  • 再添加systemEnvironment(系统环境变量)维护到父类的propertySource末尾
 //环境变量
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

//jvm 系统属性
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

 //重写父类customizePropertySources方法
 //在实例化对象的过程中调用
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    //获取systemProperties(系统属性), 添加到propertySources的末尾
	propertySources.addLast(
			new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
	//获取systemEnvironment(系统环境变量), 添加到propertySources的末尾
	propertySources.addLast(
			new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

3.1.3、StandardServletEnvironment

我们获取的环境是一个StandardServletEnvironment实例, 实例化StandardServletEnvironment的步骤有3个步骤

1、调用抽象父类AbstractEnvironment的构造函数
2、调用当前类的customizePropertySources方法

  • propertySources列表末尾添加一个名称为servletConfigInitParams的空配置
  • propertySources列表末尾再添加一个名称为servletContextInitParams的空配置
  • 如果jndi可用, propertySources列表末尾末尾在添加一个名称为jndiProperties的空配置, 由于我们没有使用jndi, 所以不会添加该配置

3、调用父类StandardEnvironment的customizePropertySources方法

  • propertySources末尾添加systemProperties(系统属性)
  • propertySources末尾获取systemEnvironment(系统环境变量)

所以当前StandardServletEnvironment对象的propertySources, 按顺序排列为servletConfigInitParams, servletContextInitParams, systemProperties, systemEnvironment

 //servlet容器初始化参数
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";

 //servlet配置初始化参数
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";

 //jndi属性
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";

protected void customizePropertySources(MutablePropertySources propertySources) {
    //末尾添加一个名称为servletContextInitParams的空配置
    propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
     //末尾再添加一个名称为servletContextInitParams的空配置
    propertySources.addLast(new StubPropertySource("servletContextInitParams"));
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        //如果jndi配置可用
        //末尾在添加一个名称为jndiProperties的空配置
        propertySources.addLast(new JndiPropertySource("jndiProperties"));
    }

    //调用父类StandardEnvironment的customizePropertySources
    super.customizePropertySources(propertySources);
}

ConfigurableEnvironment environment = getOrCreateEnvironment();执行完之后,debug如下
在这里插入图片描述
此时在 Environment 里 MutablePropertySources 类型的成员变量propertySources中已经有了四个 PropertySource 了,名称依次是:servletConfigInitParams、servletContextInitParams、systemProperties和systemEnvironment。

systemProperties配置如下:
在这里插入图片描述
systemEnvironment配置如下:
在这里插入图片描述

3.2、配置环境configureEnvironment()

configureEnvironment()方法中的逻辑也很简单。首先,为 Environment 中的 PropertySourcesPropertyResolver 设定 ConversionService;然后,向 Environment 中的 MutablePropertySources 追加一个名称为commandLineArgs的 PropertySource 实例,将defaultProperties和命令行参数分别添加到commandLineArgs的propertySources中,注意使用的是addFirst()方法,这意味着这个名称为commandLineArgs的 PropertySource 优先级是最高的。最后,设置profiles属性。主要逻辑如下:

protected void configureEnvironment(ConfigurableEnvironment environment,
            String[] args) {
    if (this.addConversionService) {
        //默认为true
        //environment中配置各个转换类
        ConversionService conversionService = ApplicationConversionService
                .getSharedInstance();
        environment.setConversionService(
                (ConfigurableConversionService) conversionService);
    }
    //2.2.1配置属性
    configurePropertySources(environment, args);
    //2.2.2配置profile
    configureProfiles(environment, args);
}

3.2.1、配置属性configurePropertySources

  • 如果默认属性defaultProperties不为空,那么将会添加到environment的propertySources末尾
  • 如果当前environment的propertySources包含commandLineArgs命令行参数, 那么替换为springApplicationCommandLineArgs
  • 如果当前environment的propertySources不包含commandLineArgs, 那么添加一个commandLineArgs到propertySources的首部, SimpleCommandLinePropertySource的代码解析在封装命令行参数DefaultApplicationArguments已经分析过了
//传入参数为StandardServletEnvironment和命令行参数
protected void configurePropertySources(ConfigurableEnvironment environment,
            String[] args) {
    //调用AbstractEnvironment的getPropertySources()方法
    //获取之前配置的所有属性
    MutablePropertySources sources = environment.getPropertySources();
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        //如果this.defaultProperties不为null
        //那么添加defaultProperties到propertySources的末尾
        sources.addLast(
                new MapPropertySource("defaultProperties", this.defaultProperties));
    }
    if (this.addCommandLineProperties && args.length > 0) {
        //如果存在命令行参数
        String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
        if (sources.contains(name)) {
            //如果sources中包含了"commandLineArgs",
            //那么将其替换为"springApplicationCommandLineArgs"
            PropertySource<?> source = sources.get(name);
            //先将"commandLineArgs"修改为null,
            CompositePropertySource composite = new CompositePropertySource(name);
            //然后新增一个PropertySource
            //name为"springApplicationCommandLineArgs",
            //source不变
            composite.addPropertySource(new SimpleCommandLinePropertySource(
                    "springApplicationCommandLineArgs", args));
            composite.addPropertySource(source);
            //替换
            sources.replace(name, composite);
        }
        else {
            //如果propertySources的中不包含"commandLineArgs"
            //将命令行参数放在propertySources的首位
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }
}

上面有个核心关键类出现了,MutablePropertySources,mutable中文是可变的意思,该类封装了属性资源集合:

public class MutablePropertySources implements PropertySources {
	private final Log logger;
	private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
	//......
}

该类又是如何使用的呢?
在这里插入图片描述
这里的设计很巧妙,将MutablePropertySources传递到文件解析器propertyResolver中,同时AbstractEnvironment又实现了文件解析接口ConfigurablePropertyResolver,所以AbstractEnvironment就有了文件解析的功能。所以StandardServletEnvironment文件解析功能实际委托给了PropertySourcesPropertyResolver来实现。
在这里插入图片描述

3.2.2、配置profile

将this.additionalProfiles和environment.getActiveProfiles()组合到一起, 重新赋值给environment的activeProfiles

//传入参数为StandardServletEnvironment和命令行参数
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    //调用的是AbstractEnvironment的getActiveProfiles()方法,读取spring.profiles.active配置的值
    environment.getActiveProfiles();
    Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
    //再次获取和配置profile
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    //设置environment的profile
    environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

为什么environment.getActiveProfiles();这个函数执行了两次?下面会有回答。

第一行获取当前环境中的spring.profiles.active属性,注意这里是到启动参数、系统配置、环境变量中找该属性,而不是加载我们项目的配置文件application.properties,此时配置文件还没开始加载
虽然有些项目也会在application.properties中指定该属性来开启不同配置,如application-dev.properties、application-sit.properties等,这个流程我们后续再介绍

 /**
 * 获取activeProfiles
 */
@Override
public String[] getActiveProfiles() {
    return StringUtils.toStringArray(doGetActiveProfiles());
}

/**
 * 获取activeProfiles
 */
protected Set<String> doGetActiveProfiles() {
    //上锁
    synchronized (this.activeProfiles) {
        if (this.activeProfiles.isEmpty()) {
            //如果activeProfiles为空
            //那么从propertySources中获取spring.profiles.active属性的值
            //并且用","分割为数组
            String profiles = getProperty("spring.profiles.active");
            if (StringUtils.hasText(profiles)) {
                //profile保存到activeProfiles成员变量中
                setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                        StringUtils.trimAllWhitespace(profiles)));
            }
        }
        return this.activeProfiles;
    }
}

在这里插入图片描述

找到的话设置到Environment的activeProfiles属性中,该属性默认为一个空的Set

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    //......省略其他代码
    private final Set<String> activeProfiles = new LinkedHashSet();
}

然后将SpringApplication的additionalProfiles也添加到Environment的avtiveProfiles列表中
这个additionalProfiles默认是空的

public class SpringApplication {
	//......省略其他代码
	private Set<String> additionalProfiles;

SpringApplication提供了一个接口来设置这个列表

public void setAdditionalProfiles(String... profiles) {
        this.additionalProfiles = new LinkedHashSet(Arrays.asList(profiles));
    }

所以我们可以通过在启动类中先new出SpringApplication,然后调用该方法来指定要激活的配置文件

@SpringBootApplication
public class Demo3Application {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(new Class[]{Demo3Application .class});
        springApplication.setAdditionalProfiles("dev");
        springApplication.run(args);
    }
}

或者在启动参数中指定spring.profiles.active属性
本文采用以下测试demo作为讲解,后续会讲解spring.profiles.active属性的作用。

@SpringBootApplication
public class Demo3Application {

    public static void main(String[] args) {
        List<String> argsList = new ArrayList<>();
        argsList.add("--spring.profiles.active=sit");
        if (args != null) {
            argsList.addAll(Arrays.asList(args));
        }
        SpringApplication.run(Demo3Application.class, StringUtils.toStringArray(argsList));
    }
}

在这里插入图片描述
或者在启动命令中添加spring.profiles.active参数
如启动命令【java -jar …/java/*.jar --spring.profiles.active=dev 】

3.3、ConfigurationPropertySources.attach(environment)

这个方法很简单,判断MutablePropertySources的成员变量propertySourceList中有没有name是configurationProperties的PropertySource对象,如果有就把它移除,然后将name是configurationProperties,类型是ConfigurationPropertySourcesPropertySource的对象添加进propertySourceList的头部

public static void attach(Environment environment) {
	Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
	 //取得environment中的propertySources
	MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
	PropertySource<?> attached = sources.get("configurationProperties");
	if (attached != null && attached.getSource() != sources) {
	    //如果存在的话,直接移除
		sources.remove("configurationProperties");
		attached = null;
	}
	if (attached == null) {
	    //将propertySources转换为SpringConfigurationPropertySources,放在首位
		sources.addFirst(new ConfigurationPropertySourcesPropertySource("configurationProperties",
				new SpringConfigurationPropertySources(sources)));
	}
}

将environment对象的propertySources属性封装成一个SpringConfigurationPropertySources对象,并以configurationProperties为key加入到propertySources属性列表的首位。

debug的时候发现一个很有意思的事情,就是configurationProperties中的ConfigurationPropertySourcesPropertySource 包含了外面所有的propertySource。原因就是在attach方法中进行了赋值。
在这里插入图片描述

3.4、environmentPrepared

发布ApplicationEnvironmentPreparedEvent事件

public void environmentPrepared(ConfigurableEnvironment environment) {
        //遍历所有的listener,发布事件。我们前面知道是SpringApplicationRunListeners实际实例化子类是EventPublishingRunListener
 	for (SpringApplicationRunListener listener : this.listeners) {
 		listener.environmentPrepared(environment);
 	}
 }

1、EventPublishingRunListener

public void environmentPrepared(ConfigurableEnvironment environment) {
 //发布ApplicationEnvironmentPreparedEvent事件(即加载配置文件)
 	this.initialMulticaster
 			.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
 }
 
 //此处即观察者模式,一直跟进去到,最终调用的是listener.onApplicationEvent(event);此处为通知加载配置文件
 public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
 	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
 	Executor executor = getTaskExecutor();
 	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
 		if (executor != null) {
 			executor.execute(() -> invokeListener(listener, event));
 		}
 		else {
 			invokeListener(listener, event);
 		}
 	}
 }

在这里插入图片描述
对该事件感兴趣的监听器有

  • ConfigFileApplicationListener:配置文件监听器,解析配置文件如propertites,yml等文件
  • AnsiOutputApplicationListener:字符输出监听器, 用于调整控制台显示的打印字符的各种颜色
  • LoggingApplicationListener:日志监听器, 初始化日志配置
  • ClasspathLoggingApplicationListener:打印debug日志, 记录当前classpath
  • BackgroundPreinitializer:
  • DelegatingApplicationListener:
  • FileEncodingApplicationListener:判断编解码是否强制

虽然有很多listener,但是监听处理ApplicationEnvironmentPreparedEvent事件的只有ConfigFileApplicationListener。

关于配置文件的核心处理便在ConfigFileApplicationListener中完成。在该类中我们可以看到很多熟悉的常量:比如Spring Boot默认寻找的配置文件的名称、默认扫描的类路径等。

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
    private static final String DEFAULT_PROPERTIES = "defaultProperties";
    private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
    private static final String DEFAULT_NAMES = "application";
    private static final Set<String> NO_SEARCH_NAMES = Collections.singleton((Object)null);
    private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class);
    public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
    public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
    public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
    public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
    public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
    public static final int DEFAULT_ORDER = -2147483638;
    private final DeferredLog logger = new DeferredLog();
    private String searchLocations;
    private String names;
    private int order = -2147483638;

    public ConfigFileApplicationListener() {
    }

3.4.1、ConfigFileApplicationListener

调用SpringFactoriesLoader.loadFactories()方法, 获取EnvironmentPostProcessor的子类列表, 然后将自己加入到子类列表中, 然后按@Order注解排序, 然后调用各个EnvironmentPostProcessor的postProcessEnvironment方法, 获取到的EnvironmentPostProcessor子类有

  • SystemEnvironmentPropertySourceEnvironmentPostProcessor
  • CloudFoundryVcapEnvironmentPostProcessor
  • SpringApplicationJsonEnvironmentPostProcessor
  • DebugAgentEnvironmentPostProcessor
  • ConfigFileApplicationListener
 @Override
 public void onApplicationEvent(ApplicationEvent event) {
       //监听的时间属于ApplicationEnvironmentPreparedEvent,则处理
 	if (event instanceof ApplicationEnvironmentPreparedEvent) {
 		onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
 	}
 	if (event instanceof ApplicationPreparedEvent) {
 		onApplicationPreparedEvent(event);
 	}
 }

 private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
       //此处从spring.factories中查找所有EnvironmentPostProcessor,(此处是一个拓展点,可以自定义自己的EnvironmentPostProcessor,加载自定义配置文件到environment中)
 	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
 	 //将this添加到postProcessors中
 	postProcessors.add(this);
 	 //通过注解Order排序
 	AnnotationAwareOrderComparator.sort(postProcessors);
       //执行postProcessors的postProcessEnvironment,此处调试有多个,我们先关注ConfigFileApplicationListener这个(即本类,可以看到它实现了EnvironmentPostProcessor接口)
 	for (EnvironmentPostProcessor postProcessor : postProcessors) {
 		postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
 	}
 }

 List<EnvironmentPostProcessor> loadPostProcessors() {
 	return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
 }

1、SystemEnvironmentPropertySourceEnvironmentPostProcessor
获取名称为systemEnvironment的属性, 替换为OriginAwareSystemEnvironmentPropertySource类型的PropertySource

public class SystemEnvironmentPropertySourceEnvironmentPostProcessor
        implements EnvironmentPostProcessor, Ordered {
        
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        //名称为systemEnvironment的属性
        String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
        PropertySource<?> propertySource = environment.getPropertySources()
                .get(sourceName);
        if (propertySource != null) {
            //替换属性
            replacePropertySource(environment, sourceName, propertySource);
        }
    }
    //替换属性
    private void replacePropertySource(ConfigurableEnvironment environment,
            String sourceName, PropertySource<?> propertySource) {
        Map<String, Object> originalSource = (Map<String, Object>) propertySource
                .getSource();
        //将systemEnvironment的属性转换为OriginAwareSystemEnvironmentPropertySource类型
        SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(
                sourceName, originalSource);
        //然后替换原有的属性
        environment.getPropertySources().replace(sourceName, source);
    }
}

2、SpringApplicationJsonEnvironmentPostProcessor
获取AbstractEnvironment的成员变量PropertySources中名称为spring.application.json的属性, 将其转换为Map, 添加到jndiProperties或者systemProperties属性之前

public class SpringApplicationJsonEnvironmentPostProcessor
        implements EnvironmentPostProcessor, Ordered {
        
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        MutablePropertySources propertySources = environment.getPropertySources();
        //将环境中spring.application.json配置转化为JsonPropertySource
        //然后添加到属性中
        propertySources.stream().map(JsonPropertyValue::get).filter(Objects::nonNull)
                .findFirst().ifPresent((v) -> processJson(environment, v));
    }

    private void processJson(ConfigurableEnvironment environment,
            JsonPropertyValue propertyValue) {
        //处理json
        JsonParser parser = JsonParserFactory.getJsonParser();
          // 获取spring.application.json 的值,转换成map
        Map<String, Object> map = parser.parseMap(propertyValue.getJson());
        if (!map.isEmpty()) {
            //默认放到jndiProperties 或者systemProperties之前
            addJsonPropertySource(environment,
                    new JsonPropertySource(propertyValue, flatten(map)));
        }
    }
}

3、CloudFoundryVcapEnvironmentPostProcessor
如果environment激活了 Cloud Founry, 那么在commandLineArgs属性之后, 添加一个vcap属性的配置

public class CloudFoundryVcapEnvironmentPostProcessor
        implements EnvironmentPostProcessor, Ordered {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        //如果environment激活了 Cloud Founry
        if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
            //那么添加对Cloud Founry的支持
            Properties properties = new Properties();
            JsonParser jsonParser = JsonParserFactory.getJsonParser();
            addWithPrefix(properties,
                    getPropertiesFromApplication(environment, jsonParser),
                    "vcap.application.");
            addWithPrefix(properties, getPropertiesFromServices(environment, jsonParser),
                    "vcap.services.");
            //然后在environment添加一个vcap的配置
            MutablePropertySources propertySources = environment.getPropertySources();
            if (propertySources.contains(
                    CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
                propertySources.addAfter(
                        CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
                        new PropertiesPropertySource("vcap", properties));
            }
            else {
                propertySources
                        .addFirst(new PropertiesPropertySource("vcap", properties));
            }
        }
    }
}

4、ConfigFileApplicationListener
在systemEnvironment属性之后, 添加random属性

 @Override
 public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
 	addPropertySources(environment, application.getResourceLoader());
 }

 protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
   //在systemEnvironment属性之后,添加random属性
 	RandomValuePropertySource.addToEnvironment(environment);
    //读取配置文件
 	new Loader(environment, resourceLoader).load();
 }

在这里插入图片描述

4.1、ConfigFileApplicationListener$Loader

//内部类, 加载配置文件 
private class Loader {
    //构造函数
    Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        this.environment = environment;
        // 创建一个占位符解析器,内部是 PropertyPlaceholderHelper,默认占位符 ${}
        this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(
                this.environment);
        //resourceLoader实例化为DefaultResourceLoader
        this.resourceLoader = (resourceLoader != null) ? resourceLoader
                : new DefaultResourceLoader();
        //此处从spring.factories中获取PropertySourceLoader的实现类,我们可以看到只有两个,如下代码PropertiesPropertySourceLoader,YamlPropertySourceLoader
        //PropertiesPropertySourceLoader加载properties文件
        //YamlPropertySourceLoader加载yml文件
        this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
                PropertySourceLoader.class, getClass().getClassLoader());
    }

    void load() {
	FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
			(defaultProperties) -> {
			    // LIFO队列 后进先出
				this.profiles = new LinkedList<>();
				this.processedProfiles = new LinkedList<>();
				// 已激活文件
				this.activatedProfiles = false;
				this.loaded = new LinkedHashMap<>();
				//初始化profiles环境信息,profiles。
				//首先添加一个null元素,如果没有设置环境,再添加一个default。目的是为了后续解析application.yml或application-default.yml
				initializeProfiles();
				while (!this.profiles.isEmpty()) {
				    //注意:这里的profile是启动参数、系统配置、环境变量中的spring.profiles.active变量,不是propertites或yml文件中的spring.profiles.active变量
				    //具体可参考本文2.2.2 configureProfiles部分
					Profile profile = this.profiles.poll();
					if (isDefaultProfile(profile)) {
						addProfileToEnvironment(profile.getName());
					}
					//遍历profile
                   // 获取默认配置文件路径,循环加载配置文件(不检查是否已经存在)
					load(profile, this::getPositiveProfileFilter,
							addToLoaded(MutablePropertySources::addLast, false));
					this.processedProfiles.add(profile);
				}
				// 对加载过的配置文件进行排序(排序就会检查是否存在)
				load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
				//将this.loaded按顺序添加到environment的propertySources中
               //如果存在defaultProperties,放在defaultProperties之前
              //如果不存在defaultProperties,直接添加到最后
				addLoadedPropertySources();
				applyActiveProfiles(defaultProperties);
			});
    }
}           

spring.factories中的PropertySourceLoader如下:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

初始化initializeProfiles()

1.查找系统变量中的 spring.profiles.active 和 spring.profiles.include 属性对应的 Profile。如没有则创建一个默认的配置文件,形如application-default.yml、application-default.properties
2.添加一个null的profile,主要用来加载没有指定profile的配置文件,比如:application.properties
因为 profiles 采用了 LIFO 队列,后进先出。所以会先加载profile为null的配置文件,也就是匹配application.properties、application.yml。

private void initializeProfiles() {
	this.profiles.add(null);
	// 取得已激活配置文件,如spring.profiles.active指定的application.yaml文件
	Set<Profile> activatedViaProperty = getProfilesFromProperty(ACTIVE_PROFILES_PROPERTY);
	Set<Profile> includedViaProperty = getProfilesFromProperty(INCLUDE_PROFILES_PROPERTY);
	List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
	this.profiles.addAll(otherActiveProfiles);
	this.profiles.addAll(includedViaProperty);
	addActiveProfiles(activatedViaProperty);
	// 如没有已激活配置文件,则新建一个默认的配置文件profiles.add("default")
	if (this.profiles.size() == 1) { // only has null profile
		for (String defaultProfileName : this.environment.getDefaultProfiles()) {
			Profile defaultProfile = new Profile(defaultProfileName, true);
			this.profiles.add(defaultProfile);
		}
	}	
}
// 取得已经指定的配置文件,如spring.profiles.active指定的yaml文件
private Set<Profile> getProfilesActivatedViaProperty() {
	// 环境中没有spring.profiles.active和spring.profiles.include指定的文件,返回空
	if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
			&& !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {
		return Collections.emptySet();
	}
	// 返回active已经激活的配置文件
	Binder binder = Binder.get(this.environment);
	Set<Profile> activeProfiles = new LinkedHashSet<>();
	activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
	activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY));
	return activeProfiles;
}

在这里插入图片描述
profile =null就是我们的默认application文件,profile=sit就是我们的application-sit文件

4.2、Loader类load方法(上述方法跟进去)

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
	//getSearchLocations()获取需要加载配置文件的目录
	//默认是四个目录"file:./config/,file:./,classpath:/config/,classpath:/"
	getSearchLocations().forEach((location) -> {
		boolean isFolder = location.endsWith("/");
		//getSearchNames()获取文件的名称,如果没有配置,默认application
		Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
		names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
	});
}
}

首先调用了getSearchLocations方法

//获取需要加载配置文件的目录
//默认是四个目录"classpath:/,classpath:/config/,file:./,file:./config/"
private Set<String> getSearchLocations() {
	Set<String> locations = getSearchLocations("spring.config.additional-location");
	if (this.environment.containsProperty("spring.config.location")) {
		locations.addAll(getSearchLocations("spring.config.location"));
	}
	else {
	    //asResolvedSet方法会对这四个目录进行反转
		locations.addAll(
				asResolvedSet(ConfigFileApplicationListener.this.searchLocations, "classpath:/,classpath:/config/,file:./,file:./config/"));
	}
	return locations;
}

1.getSearchLocations:获取配置文件路径
a)首先获取spring.config.additional-location配置的值
b) 若设置了spring.config.location属性则获取该配置的值,否则获取默认路径classpath:/,classpath:/config/,file:./,file:./config/四个路径下的值

再深入asResolvedSet方法内部分析一下

private Set<String> asResolvedSet(String value, String fallback) {
   List<String> list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(
         (value != null) ? this.environment.resolvePlaceholders(value) : fallback)));
   //对classpath:/,classpath:/config/,file:./,file:./config/进行反转
   Collections.reverse(list);
   return new LinkedHashSet<>(list);
}

这里的value表示ConfigFileApplicationListener初始化时设置的搜索路径,而fallback就是DEFAULT_SEARCH_LOCATIONS默认搜索路径。StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray())方法就是以逗号作为分隔符对"classpath:/,classpath:/config/,file:./,file:./config/"进行切割,并返回一个字符数组。而这里的Collections.reverse(list);之后,就是体现优先级的时候了,先被扫描到的配置文件会优先生效。

这里我们拿到搜索路径之后,load方法里对每个搜索路径进行遍历,首先调用了getSearchNames()方法

//获取spring.config.name配置的名称,如果没有配置,默认application
private Set<String> getSearchNames() {
	if (this.environment.containsProperty("spring.config.name")) {
		String property = this.environment.getProperty("spring.config.name");
		return asResolvedSet(property, null);
	}
	return asResolvedSet(ConfigFileApplicationListener.this.names, "application");

2.getSearchNames:获取配置文件名称
a) 若设置了spring.config.name属性则获取该配置的值,否则默认为application

在这里插入图片描述
在load方法中会遍历this.propertySourceLoaders属性,也就是上面分析的PropertiesPropertySourceLoader,YamlPropertySourceLoader两个类,然后读取对应的文件。
PropertiesPropertySourceLoader会查找properties、xml后缀的文件;
YamlPropertySourceLoader会查找yml、yaml后缀的文件
在这里插入图片描述

4.3、上述load方法跟进去,调过了两个方法,来到最终的load方法

private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
				DocumentConsumer consumer) {
	try {
		//加载location对应的文件(location为文件全路径。例:file:./config/application.properties)
		Resource resource = this.resourceLoader.getResource(location);
		if (resource == null || !resource.exists()) {
			if (this.logger.isTraceEnabled()) {
				StringBuilder description = getDescription("Skipped missing config ", location, resource,
						profile);
				this.logger.trace(description);
			}
			return;
		}
		if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
			if (this.logger.isTraceEnabled()) {
				StringBuilder description = getDescription("Skipped empty config extension ", location,
						resource, profile);
				this.logger.trace(description);
			}
			return;
		}
		String name = "applicationConfig: [" + location + "]";
		//用对应的PropertiesPropertySourceLoader或YamlPropertySourceLoader加载对应配置文件的key,value
		List<Document> documents = loadDocuments(loader, name, resource);
		if (CollectionUtils.isEmpty(documents)) {
			if (this.logger.isTraceEnabled()) {
				StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
						profile);
				this.logger.trace(description);
			}
			return;
		}
		List<Document> loaded = new ArrayList<>();
		for (Document document : documents) {
			if (filter.match(document)) {
			    //在documents里面已经解析出了propertites或yml配置文件中的key-value
			    //这里会读取spring.profiles.active属性的值并尝试重新设置Profile的值
			    //若之前initializeProfiles初始化Profile的时候没有从启动参数、系统配置、环境变量中找到spring.profiles.active属性,则这里会重新设置,否则此处不重新设置			    
				addActiveProfiles(document.getActiveProfiles());
				addIncludedProfiles(document.getIncludeProfiles());
				loaded.add(document);
			}
		}
		Collections.reverse(loaded);
		if (!loaded.isEmpty()) {
			//注意注意:此处我们会调用第一层load进来时传进来的函数式参数,即addToLoaded(MutablePropertySources::addLast, false)
			loaded.forEach((document) -> consumer.accept(profile, document));
			if (this.logger.isDebugEnabled()) {
				StringBuilder description = getDescription("Loaded config file ", location, resource, profile);
				this.logger.debug(description);
			}
		}
	}
	catch (Exception ex) {
		throw new IllegalStateException("Failed to load property source from location '" + location + "'", ex);
	}
}

在loadDocuments方法中解析出了propertites配置文件中的属性。
在这里插入图片描述

我们在启动参数中设置了spring.profiles.active=sit, 然后又在配置文件中设置了spring.profiles.active=prod, 那么这两者到底有什么区别?spring会加载application-{spring.profiles.active}.propertites最后谁生效呢?

答:若启动参数与application.propertites配置文件中同时有spring.profiles.active属性,spring默认会使用启动参数中的spring.profiles.active属性,也就是sit生效。即spring默认会加载application-sit.propertites/application-sit.yml文件。application.propertites文件中的spring.profiles.active=prod不生效。原理就在上面这段源码中

在上面【4.1、ConfigFileApplicationListener$Loader】部分我们有讲过profiles=null、sit,刚才一直加载的是profiles=null的情况,并且加载完application.propertites文件后读取到了spring.profiles.active=prod,我们再次回到主load方法,可以看到继续要加载的是启动参数中的spring.profiles.active=sit配置。
在这里插入图片描述

7、ConfigFileApplicationListener addToLoaded(MutablePropertySources::addLast, false)

//从调用点看,此处	addMethod也是一个函数MutablePropertySources::addLast(添加到最后面)
private DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,
		boolean checkForExisting) {
	return (profile, document) -> {
		if (checkForExisting) {
			for (MutablePropertySources merged : this.loaded.values()) {
				if (merged.contains(document.getPropertySource().getName())) {
					return;
				}
			}
		}
		//从当前loaded中获取profile为key的MutablePropertySources,如果没找到,则新建一个
		MutablePropertySources merged = this.loaded.computeIfAbsent(profile,
				(k) -> new MutablePropertySources());
		//把解析出来的配置,添加到MutablePropertySources中最后一个,最终配置会添加到loaded中
		addMethod.accept(merged, document.getPropertySource());
	};
}

8、回到上文中addLoadedPropertySources(),此处会把load加载的配置文件生成的PropertySource添加到environment中(此处可以看到是按什么顺序添加)

private void addLoadedPropertySources() {
 	MutablePropertySources destination = this.environment.getPropertySources();
 	List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
 	//在此处会对配置文件的顺序进行反转
 	Collections.reverse(loaded);
 	String lastAdded = null;
 	Set<String> added = new HashSet<>();
 	for (MutablePropertySources sources : loaded) {
 		for (PropertySource<?> source : sources) {
 			if (added.add(source.getName())) {
 				addLoadedPropertySource(destination, lastAdded, source);
 				lastAdded = source.getName();
 			}
 		}
 	}
 }

此处对配置文件的顺序进行了反转,虽然加载的时候先加载了application.propertites文件再加载的application-{profile}.propertites文件,但是在此处进行了反转,最后获取属性时的顺序也就变成了先从application-{profile}.propertites文件中读取属性,若属性读取不到再去application.propertites文件中进行读取。
在这里插入图片描述
反转后的顺序:
在这里插入图片描述

然后进入addLoadedPropertySource(destination, lastAdded, source);

private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded,
				PropertySource<?> source) {
	if (lastAdded == null) {
		if (destination.contains("defaultProperties")) {
			destination.addBefore("defaultProperties", source);
		}
		else {
			destination.addLast(source);
		}
	}
	else {
		destination.addAfter(lastAdded, source);
	}
}

可以看到source是被加载到了最后面。

然后environment的propertySources的propertySourceList集合就有新的数据了,我们可以看下效果
在这里插入图片描述

3.4.2、AnsiOutputApplicationListener

字符输出监听器, 用于调整控制台显示的打印字符的各种颜色

public class AnsiOutputApplicationListener
        implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        //控制台打印字符显示各种颜色
        //例如:设置spring.output.ansi.enabled=ALWAYS
        Binder.get(environment)
                .bind("spring.output.ansi.enabled", AnsiOutput.Enabled.class)
                .ifBound(AnsiOutput::setEnabled);
        AnsiOutput.setConsoleAvailable(environment
                .getProperty("spring.output.ansi.console-available", Boolean.class));
    }
}

3.4.3、LoggingApplicationListener

日志监听器, 初始化日志配置

public class LoggingApplicationListener implements GenericApplicationListener {
    //处理EnvironmentPrepared事件
    private void onApplicationEnvironmentPreparedEvent(
            ApplicationEnvironmentPreparedEvent event) {
        if (this.loggingSystem == null) {
            //在Starting事件中,我们已经初始化了loggingSystem
            //我们使用的是LogbackLoggingSystem
            this.loggingSystem = LoggingSystem
                    .get(event.getSpringApplication().getClassLoader());
        }
        initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
    }
    //初始化日志
    protected void initialize(ConfigurableEnvironment environment,
        ClassLoader classLoader) {
        //应用环境属性
        new LoggingSystemProperties(environment).apply();
        LogFile logFile = LogFile.get(environment);
        if (logFile != null) {
            logFile.applyToSystemProperties();
        }
        //应用debug/trace参数
        initializeEarlyLoggingLevel(environment);
        //加载日志配置文件,应用环境属性
        initializeSystem(environment, this.loggingSystem, logFile);
        //根据环境属性设置日志输出级别
        initializeFinalLoggingLevels(environment, this.loggingSystem);
        //注册shutdown处理方式
        registerShutdownHookIfNecessary(environment, this.loggingSystem);
    }
}

3.4.4、ClasspathLoggingApplicationListener

打印debug日志, 记录当前classpath

public final class ClasspathLoggingApplicationListener
        implements GenericApplicationListener {

    private static final int ORDER = LoggingApplicationListener.DEFAULT_ORDER + 1;

    private static final Log logger = LogFactory
            .getLog(ClasspathLoggingApplicationListener.class);

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (logger.isDebugEnabled()) {
            if (event instanceof ApplicationEnvironmentPreparedEvent) {
                //处理EnvironmentPrepared事件
                //debug记录当前classpath
                logger.debug("Application started with classpath: " + getClasspath());
            }
            else if (event instanceof ApplicationFailedEvent) {
                logger.debug(
                        "Application failed to start with classpath: " + getClasspath());
            }
        }
    }
    
    private String getClasspath() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if (classLoader instanceof URLClassLoader) {
            //获取classLoader加载类和资源的搜索路径
            return Arrays.toString(((URLClassLoader) classLoader).getURLs());
        }
        return "unknown";
    }
}

3.4.5、BackgroundPreinitializer

扩展点, 目前只关注Starting, Ready和Failed事件, EnvironmentPrepared事件不做处理

3.4.6、DelegatingApplicationListener 委托监听器

扩展点, 将当前的EnvironmentPrepared事件, 广播给其他关注该事件的Environment监听器, 目前没有做任何操作

public class DelegatingApplicationListener
        implements ApplicationListener<ApplicationEvent>, Ordered {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
                    ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
            if (delegates.isEmpty()) {
                //如果delegates为空,则立即返回
                return;
            }
            this.multicaster = new SimpleApplicationEventMulticaster();
            for (ApplicationListener<ApplicationEvent> listener : delegates) {
                this.multicaster.addApplicationListener(listener);
            }
        }
        if (this.multicaster != null) {
            this.multicaster.multicastEvent(event);
        }
    }
}

3.4.7、FileEncodingApplicationListener

如果指定了spring.mandatory-file-encoding属性, 如果系统属性file.encoding和spring.mandatory-file-encoding不一致的话, 抛出异常

public class FileEncodingApplicationListener
        implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        if (!environment.containsProperty("spring.mandatory-file-encoding")) {
            //如果环境不包含spring.mandatory-file-encoding属性
            //则立即返回
            return;
        }
        //获取系统指定编码
        String encoding = System.getProperty("file.encoding");
        String desired = environment.getProperty("spring.mandatory-file-encoding");
        if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
            //系统编码和指定编码不一致,那么报错
            logger.error("System property 'file.encoding' is currently '" + encoding
                    + "'. It should be '" + desired
                    + "' (as defined in 'spring.mandatoryFileEncoding').");
            logger.error("Environment variable LANG is '" + System.getenv("LANG")
                    + "'. You could use a locale setting that matches encoding='"
                    + desired + "'.");
            logger.error("Environment variable LC_ALL is '" + System.getenv("LC_ALL")
                    + "'. You could use a locale setting that matches encoding='"
                    + desired + "'.");
            throw new IllegalStateException(
                    "The Java Virtual Machine has not been configured to use the "
                            + "desired default character encoding (" + desired + ").");
        }
    }
}

3.5、bindToSpringApplication绑定环境

//如果指定了main函数,那么会将当前环境绑定到指定的SpringApplication中
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));

Binder是SpringBoot 2.X引入的新特性,用于将environment的属性绑定到目标类上。
这里就相当于将spring.main开头的属性,绑定到SpringApplicaiton对象上,前提是SpringApplication提供了相应属性的set方法
比如常见的配置spring.main.allow-bean-definition-overriding=true,SpringApplication类存在该属性,且有对应的set方法,那么就会将配置文件中的值赋给该属性

public class SpringApplication {
	//......省略其他代码
	private boolean allowBeanDefinitionOverriding;
	
	public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) {
        this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding;
    }

3.6、环境转换EnvironmentConverter

如果没有调用了setEnvironment方法设置了环境, 那么将自定义environment转换为StandardEnvironment

//环境转换
if (!this.isCustomEnvironment) {
	environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
			deduceEnvironmentClass());
}
final class EnvironmentConverter {
    //环境转换
    StandardEnvironment convertEnvironmentIfNecessary(ConfigurableEnvironment environment,
            Class<? extends StandardEnvironment> type) {
        if (type.equals(environment.getClass())) {
            return (StandardEnvironment) environment;
        }
        //environment.getClass()不是StandardEnvironment的实例
        return convertEnvironment(environment, type);
    }
    
    //环境转换
    private StandardEnvironment convertEnvironment(ConfigurableEnvironment environment,
            Class<? extends StandardEnvironment> type) {
        //新建一个StandardEnvironment实例
        //然后赋值
        StandardEnvironment result = createEnvironment(type);
        result.setActiveProfiles(environment.getActiveProfiles());
        result.setConversionService(environment.getConversionService());
        copyPropertySources(environment, result);
        return result;
    }
}

四、总结

1、调用getOrCreateEnvironment()方法

  • 创建了StandardServletEnvironment的实例
  • 在AbstractEnvironment的propertySources中按顺序添加了名称为servletConfigInitParams, servletContextInitParams, jndiProperties, systemProperties, systemEnvironment的属性

2、调用configurePropertySources()方法

  • 在AbstractEnvironment的propertySources末尾添加defaultProperties属性配置
  • 添加commandLineArgs,到propertySources首部
  • 获取spring.profiles.active属性

3、发布ApplicationEnvironmentPreparedEvent事件, 支持该事件的监听器有

  • ConfigFileApplicationListener获取了五个EnvironmentPostProcessor, 分别执行他们的postProcessEnvironment方法
    1. SystemEnvironmentPropertySourceEnvironmentPostProcessor 将systemEnvironment属性的类型替换为SystemEnvironmentPropertySource
    2. SpringApplicationJsonEnvironmentPostProcessor, 添加spring.application.json属性到jndiProperties或者systemProperties之前
    3. CloudFoundryVcapEnvironmentPostProcessor 添加vcap属性到commandLineArgs之后
    4. ConfigFileApplicationListener, 首先添加random属性到systemEnvironment之后, 再读取配置文件, 加载到defaultProperties属性之前
    5. AnsiOutputApplicationListener 配置控制台打印字符串的颜色 LoggingApplicationListener 初始化日志配置
    6. ClasspathLoggingApplicationListener debug打印classpath日志 BackgroundPreinitializer 没有做任何操作
    7. DelegatingApplicationListener 没有做任何操作
    8. FileEncodingApplicationListener 判断file.encoding和spring.mandatory-file-encoding是否一致

    4、将当前环境绑定到spring.main配置的main函数中
    5、如果设置好了自定义environment, 那么将其转换为StandardEnvironment
    6、如果配置了configurationProperties属性, 那么将其放在environment的propertySources的首部
    7、environment的propertySources中属性列表顺序为configurationProperties, commandLineArgs, vcap, servletConfigInitParams, servletContextInitParams, spring.application.json, jndiProperties, systemProperties, systemEnvironment, random, 配置文件, defaultProperties

    五、问答

    5.1、ConfigurationPropertySources.attach为什么调用两次?

    如下:在prepareEnvironment中调用了两次ConfigurationPropertySources.attach方法

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    													   ApplicationArguments applicationArguments) {
    	// 获取或者创建环境   创建就会读取: java环境变量和系统环境变量
    	ConfigurableEnvironment environment = getOrCreateEnvironment();
    	//配置环境  将命令行参数读取环境变量中
    	//将 defaultProperties、commandLine及active-prifiles 属性加载到环境中
        //commandLine 在 args 中配置
        //其它参数可在如下4个路径中配置:servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment
    	configureEnvironment(environment, applicationArguments.getSourceArgs());
    	// 将@PropertieSource的配置信息 放在第一位,它的优先级是最低的
    	ConfigurationPropertySources.attach(environment);
    	//发布环境已准备事件,这是第二次发布事件,发布了ApplicationEnvironmentPreparedEvent 的监听器 读取了全局配置文件
    	listeners.environmentPrepared(environment);
    	// 将所有spring.main 开头的配置信息绑定到SpringApplication中
    	bindToSpringApplication(environment);
    	//如果不是web应用,就转成其他环境如StandardEnvironment
    	if (!this.isCustomEnvironment) {
    		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
    				deduceEnvironmentClass());
    	}
    	//绑定环境
    	ConfigurationPropertySources.attach(environment);
    	return environment;
    }
    

    第一次ConfigurationPropertySources.attach是为了将configurationProperties添加到MutablePropertySources首位, 然后发送listeners.environmentPrepared, 在后面的convertEnvironmentIfNecessary方法中可能会修改到configurationProperties属性,所以再次ConfigurationPropertySources.attach

    5.2、为什么environment.getActiveProfiles();这个函数执行了两次?

    //传入参数为StandardServletEnvironment和命令行参数
    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        //调用的是AbstractEnvironment的getActiveProfiles()方法
        environment.getActiveProfiles();
        Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
        //再次获取和配置profile
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        //设置environment的profile
        environment.setActiveProfiles(StringUtils.toStringArray(profiles));
    }
    

    可以看到environment.getActiveProfiles();这个函数执行了两次,且其内部实现并没有特殊之处

    /**
    * Return the set of active profiles as explicitly set through
     * {@link #setActiveProfiles} or if the current set of active profiles
     * is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME}
     * property and assign its value to the set of active profiles.
     * @see #getActiveProfiles()
     * @see #ACTIVE_PROFILES_PROPERTY_NAME
     */
    protected Set<String> doGetActiveProfiles() {
    	synchronized (this.activeProfiles) {
    		if (this.activeProfiles.isEmpty()) {
    			String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
    			if (StringUtils.hasText(profiles)) {
    				setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
    						StringUtils.trimAllWhitespace(profiles)));
    			}
    		}
    		return this.activeProfiles;
    	}
    }
    

    按照我的理解调用两次是确保environment的activeProfiles属性确实从之前配置好的PropertySources中读取了spring.profiles.active属性,也就是说对PropertySourcesPropertyResolver类的getProperty函数的实现是悲观的。


    后续问题
    看完本篇文章后,我们还有其他的疑问。
    1)prepareEnvironment这一步骤中会加载classpath:/,classpath:/config/,file:./,file:./config/四个目录下的配置文件,优先级是怎样的?
    2)若不同的配置文件中有相同的变量名,以前面的为准还是以后面的为准?这些问题我们都将在下一篇文章中进行讲解。




    创作不易,欢迎打赏,你的鼓励将是我创作的最大动力。

    在这里插入图片描述

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包

    打赏作者

    五月天的尾巴

    你的鼓励将是我创作的最大动力

    ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
    扫码支付:¥1
    获取中
    扫码支付

    您的余额不足,请更换扫码支付或充值

    打赏作者

    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值