前言
本文主要是接上文源碼走讀之一中
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的重點部分了,跟spring真正結合的地方來了。
即如下代碼
//創建spring context
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//【重點中的重點】調用spring的refresh方法,進行spring的bean的加載。
refreshContext(context);
下一篇我們將詳細介紹spring的context的初始化。這里需要對spring的源碼及原理有一些了解。