上篇文章分析了ApplicationStartingEvent事件的处理,我们回到SpringApplication.run()方法的流程中
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
........
........
........
}
可以看到try之前的代码我们都已经分析过了,接下来我们看下try模块中的第一行代码,将启动参数args传给了DefaultApplicationArguments类的构造方法
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
我们将通过对这行代码的分析,引出一个比较重要的概念:PropertySource
先看下DefaultApplicationArguments类的结构
public class DefaultApplicationArguments implements ApplicationArguments {
private final DefaultApplicationArguments.Source source;
private final String[] args;
public DefaultApplicationArguments(String[] args) {
Assert.notNull(args, "Args must not be null");
this.source = new DefaultApplicationArguments.Source(args);
this.args = args;
}
内部类Source是一个PropertySource的派生类,继承结构如图
PropertySource可以看作是一组属性的集合,name是这组属性的组名,source是属性的载体,可以是Map,可以是List,也可以是复杂的自定义类型,总而言之就是存储了一系列属性,具体用什么存储,子类自己去决定
public abstract class PropertySource<T> {
......
protected final String name;
protected final T source;
后面我们会看到很多PropertySource的派生类,存储系统属性、系统环境变量、配置文件属性等等
下图列举了一些常用的派生类
不同的子类可能使用了不同的存储结构,也会对PropertySource做一些扩展,比如EnumerablePropertySource
public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
......
......
public abstract String[] getPropertyNames();
}
从名字来看,它是可枚举的PropertySource,提供了一个获取所有属性名的方法,由于它也没有确定属性存储的结构,所以如何枚举属性,也是交给子类去实现
我们看一个它的派生类MapPropertySource
public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
......
......
public String[] getPropertyNames() {
return StringUtils.toStringArray(((Map)this.source).keySet());
}
}
它通过类型参数指定了属性的存储结构为Map,所以遍历属性名自然就是获取map的keySet了
后续的流程会见到各种各样的PropertySource,可以简单地把他们看作一组属性的容器,根据属性的来源、特点、用途等,用不同的容器来存储
回到SpringBoot的源码来,我们这里遇到的PropertySource是一个内部类,它继承自SimpleCommandLinePropertySource ,这个类又继承了CommandLinePropertySource,并指定了属性的载体类型为CommandLineArgs,在构造函数中对系统的启动参数做了解析
private static class Source extends SimpleCommandLinePropertySource {
Source(String[] args) {
super(args);
}
......
}
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
public SimpleCommandLinePropertySource(String... args) {
super((new SimpleCommandLineArgsParser()).parse(args));
}
先看下CommandLineArgs的结构,有一个Map和一个List
class CommandLineArgs {
private final Map<String, List<String>> optionArgs = new HashMap();
private final List<String> nonOptionArgs = new ArrayList();
解析的方法parse,简而言之,就是遍历启动参数,如果是 - -开头,再根据=号切割成key-value,添加到map,map的value也是一个List,如果是- -开头的参数,可以用同一个参数名传递多个值,不同值存储在参数名对应的List中
如果没有- -开头,直接添加到List
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;
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;
}
两个add方法
public void addOptionArg(String optionName, @Nullable String optionValue) {
if (!this.optionArgs.containsKey(optionName)) {
this.optionArgs.put(optionName, new ArrayList());
}
if (optionValue != null) {
((List)this.optionArgs.get(optionName)).add(optionValue);
}
}
public void addNonOptionArg(String value) {
this.nonOptionArgs.add(value);
}
然后在父类的构造方法中,指定了组名为commandLineArgs
public CommandLinePropertySource(T source) {
super("commandLineArgs", source);
}
总结来说,new出来的DefaultApplicationArguments就是用来存储启动参数,也即命令行参数
比如通过 java -jar 命令指定的参数,会传递给启动函数的args中,再转交给DefaultApplicationArguments来存储
debug验证一下,修改SpringBoot应用的启动方法
@SpringBootApplication
public class Application {
public static void main(String[] args) {
System.out.println("======命令行参数=======");
Stream.of(args).forEach(System.out::println);
System.out.println("======命令行参数=======");
SpringApplication.run(Application.class, args);
}
}
通过java -jar 指令,添加参数 - -test.param=11111,从日志里看到main方法接收到参数
最终被解析成如下结构