Spring Boot外化配置源码解析
外化配置简介
Spring Boot设计了非常特殊的加载指定属性文件(PropertySouce)的顺序,允许属性值合理的覆盖,属性值会以下面的优先级进行配置。
- home目录下的Devtool全局设置属性(~/.spring-boot-devtools.properties,条件是当devtools激活时)
- @TestPropertySource注解的测试用例。
- @SpringBootTest#properties注解的测试用例。
- 命令行参数。
- 来自SPRING_APPLICATION_JSON的属性(内嵌在环境变量或系统属性中的内联JSON)
- ServletConfig初始化参数
- ServletContext初始化参数
- java:comp/env的JNDI属性
- Java系统属性(System.getProperties())
- 操作系统环境变量
- RandomValuePropertySource,只包含random.*中的属性
- jar包外的Profile_specific应用属性(application-{profile}.propertis和YAML变量)
- jar包内的Profile_specific应用属性(application-{profile}.propertis和YAML变量)
- jar包外的应用配置(application.properties和YAML变量)
- jar包内的应用配置(application.properties和YAML变量)
- @Configuration类上的@PropertySource注解
- 默认属性(通过SpringApplication.setDefaultProperties指定)
在以上配置方式中,我们经常使用的包括:命令参数,属性文件,YAML文件等内容,以下将围绕他们的运行及相关代码进行讲解。
ApplicationArguments参数处理
ApplicationArguments提供了针对参数的解析和查询功能。在Spring Boot运行阶段的章节中我们提到过,通过SpringApplication.run(args)传递的参数会被封装在ApplicationArguments接口中。本节我们来详细了解下ApplicationArguments接口。
接口定义及初始化
首先看一下ApplicationArguments接口的具体方法定义及功能介绍。
package org.springframework.boot;
import java.util.List;
import java.util.Set;
public interface ApplicationArguments {
//返回原始未处理的参数(通过application传入的)
String[] getSourceArgs();
//返回所有参数的集合,如参数为:--foo=bar --debug,则返回【"foo","debug"】
Set<String> getOptionNames();
//选项参数中是否包含指定名称的参数
boolean containsOption(String name);
//根据选项参数的名称获取选项参数的值列表
List<String> getOptionValues(String name);
//返回非选项参数列表
List<String> getNonOptionArgs();
}
通过接口定义可以看出,ApplicationArguments主要提供了针对参数名称和值的查询,以及判断是否存在指定参数的功能。
在Spring Boot的初始化运行过程中,ApplicationArguments接口的实例化操作默认是通过实现类DefaultApplicationArguments来完成的。DefaultApplicationArguments的底层又是基于Spring框架中的命令行配置源SimpleCommandLinePropertySource实现的,SpringCommandLinePropertySource是PropertySource抽象类的派生类。
以下代码中内部类Source便是SimppleCommandLinePropertySource的子类。
public class DefaultApplicationArguments implements ApplicationArguments {
private final Source source;
private final String[] args;
public DefaultApplicationArguments(String... args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
......
private static class Source extends SimpleCommandLinePropertySource {
......
}
}
我们再来看SimpleCommandLinePropertySource的构造方法,通过代码会发现默认使用spring的SimpleCommandLineArgsParser对args参加进行解析。
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
public SimpleCommandLinePropertySource(String... args) {
super((new SimpleCommandLineArgsParser()).parse(args));
}
//重载的构造方法
public SimpleCommandLinePropertySource(String name, String[] args) {
super(name, (new SimpleCommandLineArgsParser()).parse(args));
}
......
}
除了构造方法之外,SimpleCommandLinePropertySource还提供了不同类型参数信息的获取和检查是否存在的功能,代码如下:
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
......
//获取选项参数数组
public String[] getPropertyNames() {
return StringUtils.toStringArray(((CommandLineArgs)this.source).getOptionNames());
}
//获取是否包含指定name的参数
protected boolean containsOption(String name) {
return ((CommandLineArgs)this.source).containsOption(name);
}
//获取指定name的选项参数列表
@Nullable
protected List<String> getOptionValues(String name) {
return ((CommandLineArgs)this.source).getOptionValues(name);
}
//获取非选项参数列表
protected List<String> getNonOptionArgs() {
return ((CommandLineArgs)this.source).getNonOptionArgs();
}
}
ApplicatinArguments,或者更进一步说是SimpleCommandLinePropertySource对参数类型是有所区分的,即选项参数和非选项参数。
选项参数必须以“–”为前缀,参数值可为空,该参数可以通过Spring Boot属性处理后使用,比如在执行jar -jar命令时,添加选项参数“–app.name=spring boot start",在代码中可以通过注解@Value属性及其他方式获取到该参数的值。该参数可以通过逗号分隔多个参数值,或者多次使用同一个参数来包含多个参数的值。
非选项参数并不要求以“–”前缀开始,可自行定义。非选项参数可以直接在jar -jar命令中定义参数为“non-option"的参数值。
以上所说的选项参数和非选项参数的解析是在SimpleCommandLinePropertySource构造方法中调用SimpleCommandLineArgsParser中完成的,代码如下:
class SimpleCommandLineArgsParser {
SimpleCommandLineArgsParser() {
}
//解析args参数,返回一个完整的CommandLineArgs对象
public CommandLineArgs parse(String... args) {
CommandLineArgs commandLineArgs = new CommandLineArgs();
String[] var3 = args;
int var4 = args.length;
//遍历参数
for(int var5 = 0; var5 < var4; ++var5) {
String arg = var3[var5];
//解析选项参数,以"--"开头
if (arg.startsWith("--")) {
String optionText = arg.substring(2, arg.length());
String optionValue = null;
String optionName;
//判断是--foo=bar参数格式,还是-foo参数格式,并分别处理获取值
if (optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf(61));
optionValue = optionText.substring(optionText.indexOf(61) + 1, optionText.length());
} else {
optionName = optionText;
}
if (optionName.isEmpty() || optionValue != null && optionValue.isEmpty()) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
commandLineArgs.addOptionArg(optionName, optionValue);
} else {
//处理非选项参数
commandLineArgs.addNonOptionArg(arg);
}
}
return commandLineArgs;
}
}
通过SimpleCommandLineArgsParser的代码可以看出,Spring对参数的解析是按照指定的参数格式分别解析字符串中的值来实现的。最终,解析的结果均封装在CommandLineArgs中。而CommandLineArgs类只是命令行参数的简单表示形式,内部分为“选项参数”和"非选项参数"
class CommandLineArgs {
private final Map<String, List<String>> optionArgs