springboot源码走读之二----命令行参数封装与上下文环
前言
本文主要是接上文源码走读之一中
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
这段代码而来。主要走读
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
这两步代码。
首先来看第一步,参数封装。
ApplicationArguments生成
ApplicationArguments
的主要处理逻辑在于SimpleCommandLineArgsParser
中parse
方法。方法实现如下:
public CommandLineArgs parse(String... args) {
CommandLineArgs commandLineArgs = new CommandLineArgs();
for (String arg : args) {
if (arg.startsWith("--")) {
String optionText = arg.substring(2, arg.length());
String optionName;
String optionValue = null;
if (optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf('='));
optionValue = optionText.substring(optionText.indexOf('=')+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;
}
这里springboot命令行参数包括两种,一种是以--
开头的参数,类似key/value
形式的。如--profile,--log.level
等。一种是直接以value
形式的,如foo
等。具体参数的意义,跟业务相关。
最后这个commandLineArgs
被放到什么地方了呢?看如下代码:
public DefaultApplicationArguments(String... args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
我们进一步进入Source中,找到如下代码:
public PropertySource(String name, T source) {
Assert.hasText(name, "Property source name must contain at least one character");
Assert.notNull(source, "Property source must not be null");
this.name = name;
this.source = source;
}
这里初始化一个PropertySource,name
为:commandLineArgs
,value就为上面初始化的CommandLineArgs
。
这里,只要业务能获取到applicationArguments
,就能获取到我们从命令行中传入的参数。
通常,我们会通过environment
来获取参数。例子如下:
ApplicationContext ctx = SpringApplication.run(StudyApplication.class);
System.out.println(ctx.getEnvironment().getProperty("foo"));
这里就会在控制台中打印出key为“foo”的值。那么,environment是如何构造的呢?这就到了接下来要看的代码中了,即environment
的构造。
Environment的构造
Environment
的主要代码如下:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 创建一个environment对象。如果是`SERVLET`,则new一个StandardServletEnvironment,如果是`REACTIVE`,则new一个StandardReactiveWebEnvironment,否则初始化一个StandardEnvironment。初始化对象的时候,创建一些java标准的环境参数,如`systemEnvironment`和`systemProperties`及对应容器特有的环境参数,如`servletConfigInitParams`和`servletContextInitParams`
ConfigurableEnvironment environment = getOrCreateEnvironment();
//该步骤为设置一些默认的converter和formatter及设置命令行参数到environment中,还有设置应用程序中设置的profile及active profile。
configureEnvironment(environment, applicationArguments.getSourceArgs());
//配置configurationProperties的值
ConfigurationPropertySources.attach(environment);
//触发环境准备完成事件,事件的消费者在springboot的代码中,有如下地方:
// FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener【重要,加载配置文件的地方,下面会专门讲】,LoggingApplicationListener 日志系统,WebEnvironmentPropertySourceInitializer
listeners.environmentPrepared(environment);
//绑定环境到springapplication上。spring.main配置【具体作用,以后用到的时候在做说明】
bindToSpringApplication(environment);
//如果不是自定义环境,则转换之。
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
到此,环境准备完成。
总结代码,环境准备主要做了如下几件事情:
1.确定active环境。
2.确定环境参数,jvm参数及os参数。
3.加载properties,yml配置文件。
4.确定spring.main配置。
configureIgnoreBeanInfo
这里设置spring.beaninfo.ignore
的值,若没有指定,则设置为true。具体用处,有待进一步走读代码
printBanner
这一步是打印banner图,即
springboot banner图
接下来,真正进入springboot的重点部分了,跟spring真正结合的地方来了。
即如下代码
//创建spring context
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//【重点中的重点】调用spring的refresh方法,进行spring的bean的加载。
refreshContext(context);
下一篇我们将详细介绍spring的context的初始化。这里需要对spring的源码及原理有一些了解。