前言
本文主要是接上文源碼走讀之一中
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);
這兩步代碼婿禽。
首先來看第一步盖矫,參數(shù)封裝。
ApplicationArguments生成
ApplicationArguments
的主要處理邏輯在于SimpleCommandLineArgsParser
中parse
方法岗仑。方法實現(xiàn)如下:
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命令行參數(shù)包括兩種,一種是以--
開頭的參數(shù)聚请,類似key/value
形式的荠雕。如--profile,--log.level
等。一種是直接以value
形式的驶赏,如foo
等炸卑。具體參數(shù)的意義,跟業(yè)務(wù)相關(guān)煤傍。
最后這個commandLineArgs
被放到什么地方了呢盖文?看如下代碼:
public DefaultApplicationArguments(String... args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
我們進(jìn)一步進(jìn)入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
。
這里龄恋,只要業(yè)務(wù)能獲取到applicationArguments
疙驾,就能獲取到我們從命令行中傳入的參數(shù)。
通常篙挽,我們會通過environment
來獲取參數(shù)荆萤。例子如下:
ApplicationContext ctx = SpringApplication.run(StudyApplication.class);
System.out.println(ctx.getEnvironment().getProperty("foo"));
這里就會在控制臺中打印出key為“foo”的值。那么铣卡,environment是如何構(gòu)造的呢链韭?這就到了接下來要看的代碼中了,即environment
的構(gòu)造煮落。
Environment的構(gòu)造
Environment
的主要代碼如下:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 創(chuàng)建一個environment對象敞峭。如果是`SERVLET`,則new一個StandardServletEnvironment,如果是`REACTIVE`,則new一個StandardReactiveWebEnvironment蝉仇,否則初始化一個StandardEnvironment旋讹。初始化對象的時候殖蚕,創(chuàng)建一些java標(biāo)準(zhǔn)的環(huán)境參數(shù),如`systemEnvironment`和`systemProperties`及對應(yīng)容器特有的環(huán)境參數(shù)沉迹,如`servletConfigInitParams`和`servletContextInitParams`
ConfigurableEnvironment environment = getOrCreateEnvironment();
//該步驟為設(shè)置一些默認(rèn)的converter和formatter及設(shè)置命令行參數(shù)到environment中睦疫,還有設(shè)置應(yīng)用程序中設(shè)置的profile及active profile。
configureEnvironment(environment, applicationArguments.getSourceArgs());
//配置configurationProperties的值
ConfigurationPropertySources.attach(environment);
//觸發(fā)環(huán)境準(zhǔn)備完成事件鞭呕,事件的消費者在springboot的代碼中蛤育,有如下地方:
// FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener【重要,加載配置文件的地方葫松,下面會專門講】瓦糕,LoggingApplicationListener 日志系統(tǒng),WebEnvironmentPropertySourceInitializer
listeners.environmentPrepared(environment);
//綁定環(huán)境到springapplication上腋么。spring.main配置【具體作用咕娄,以后用到的時候在做說明】
bindToSpringApplication(environment);
//如果不是自定義環(huán)境,則轉(zhuǎn)換之珊擂。
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
到此圣勒,環(huán)境準(zhǔn)備完成。
總結(jié)代碼未玻,環(huán)境準(zhǔn)備主要做了如下幾件事情:
1.確定active環(huán)境灾而。
2.確定環(huán)境參數(shù),jvm參數(shù)及os參數(shù)扳剿。
3.加載properties,yml配置文件昼激。
4.確定spring.main配置庇绽。
configureIgnoreBeanInfo
這里設(shè)置spring.beaninfo.ignore
的值,若沒有指定橙困,則設(shè)置為true瞧掺。具體用處,有待進(jìn)一步走讀代碼
printBanner
這一步是打印banner圖凡傅,即
接下來辟狈,真正進(jìn)入springboot的重點部分了,跟spring真正結(jié)合的地方來了夏跷。
即如下代碼
//創(chuàng)建spring context
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//【重點中的重點】調(diào)用spring的refresh方法哼转,進(jìn)行spring的bean的加載。
refreshContext(context);
下一篇我們將詳細(xì)介紹spring的context的初始化槽华。這里需要對spring的源碼及原理有一些了解壹蔓。