遇到一個問題,需要從yml文件中讀取數(shù)據(jù)初始化到static的類中功咒。搜索需要實現(xiàn)ApplicationRunner,并在其實現(xiàn)類中把值讀出來再set進(jìn)去。于是乎就想探究一下SpringBoot啟動中都干了什么比默。
引子
就像引用中說的,用到了ApplicationRunner類給靜態(tài)class賦yml中的值盆犁。代碼先量一下命咐,是這樣:
@Data
@Component
@EnableConfigurationProperties(MyApplicationRunner.class)
@ConfigurationProperties(prefix = "flow")
public class MyApplicationRunner implements ApplicationRunner {
private String name;
private int age;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner...start...");
MyProperties.setAge(age);
MyProperties.setName(name);
System.out.println("ApplicationRunner...end...");
}
}
public class MyProperties {
private static String name;
private static int age;
public static String getName() {
return name;
}
public static void setName(String name) {
MyProperties.name = name;
}
public static int getAge() {
return age;
}
public static void setAge(int age) {
MyProperties.age = age;
}
}
從SpringApplication開始
@SpringBootApplication
public class FlowApplication {
public static void main(String[] args) {
SpringApplication.run(FlowApplication.class, args);
}
}
這是一個SpringBoot啟動入口,整個項目環(huán)境搭建和啟動都是從這里開始的谐岁。我們就從SpringApplication.run()點進(jìn)去看一下侈百,Spring Boot啟動的時候都做了什么。點進(jìn)去run看一下翰铡。
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
首先經(jīng)過了兩個方法钝域,馬上就要進(jìn)入關(guān)鍵了。SpringApplication(primarySources).run(args)锭魔,這句話做了兩件事例证,首先初始化SpringApplication,然后進(jìn)行開啟run迷捧。首先看一下初始化做了什么织咧。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
首先讀取資源文件Resource,然后讀取FlowApplication這個類信息(就是primarySources)胀葱,然后從classPath中確定是什么類型的項目,看一眼WebApplicationType這里面有三種類型:
public enum WebApplicationType {
NONE, //不是web項目
SERVLET,//是web項目
REACTIVE;//2.0之后新加的笙蒙,響應(yīng)式項目
...
}
回到SpringApplication接著看抵屿,確定好項目類型之后,初始化一些信息setInitializers()捅位,getSpringFactoriesInstances()看一下都進(jìn)行了什么初始化:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
首先得到ClassLoader轧葛,這個里面記錄了所有項目package的信息、所有calss的信息啊什么什么的艇搀,然后初始化各種instances尿扯,在排個序,ruturn之焰雕。
再回到SpringApplication衷笋,接著是設(shè)置監(jiān)聽器setListeners()。
然后設(shè)置main方法矩屁,mainApplicationClass()辟宗,點進(jìn)deduceMainApplicationClass()看一看:
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
從方法棧stackTrace中,不斷讀取方法吝秕,通過名稱泊脐,當(dāng)讀到“main”方法的時候,獲得這個類實例郭膛,return出去晨抡。
到這里,所有初始化工作結(jié)束了则剃,也找到了Main方法耘柱,ruturn給run()方法,進(jìn)行后續(xù)項目的項目啟動棍现。
準(zhǔn)備好调煎,開始run吧
先上代碼:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
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);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
首先開啟一個計時器,記錄下這次啟動時間己肮,咱們項目開啟 XXXms statred 就是這么計算來的士袄。
然后是一堆聲明,知道listeners.starting()谎僻,這個starting()娄柳,我看了一下源碼注釋
Called immediately when the run method has first started. Can be used for very
early initialization.
早早初始化,是為了后面使用艘绍,看到后面還有一個方法listeners.started()
Called immediately before the run method finishes, when the application context has been refreshed and all {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner ApplicationRunners} have been called.
這會兒應(yīng)該才是真正的開啟完畢赤拒,值得一提的是,這里終于看到了引子中的ApplicationRunner這個類了,莫名的有點小激動呢挎挖。
我們繼續(xù)進(jìn)入try这敬,接下來是讀取一些參數(shù)applicationArguments,然后進(jìn)行l(wèi)istener和environment的一些綁定蕉朵。然后打印出Banner圖崔涂,printBanner(),這個方法里面可以看到把environment,也存入Banner里面了始衅,應(yīng)該是為了方便打印冷蚂,如果有日志模式,也打印到日志里面觅闽,所以帝雇,項目啟動的打印日志里面記錄了很多東西涮俄。
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null)
? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
接著 生成上下文環(huán)境 context = createApplicationContext();還記著webApplicationType三種類型嗎蛉拙,這邊是根據(jù)webApplicationType類型生成不同的上下文環(huán)境類的。
接著開啟 exceptionReporters彻亲,用來支持啟動時的報錯孕锄。
接著就要準(zhǔn)備往上下文中set各種東西了,看prepareContext()方法:
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
首先把環(huán)境environment放進(jìn)去苞尝,然后把resource信息也放進(jìn)去畸肆,再讓所有的listeners知道上下文環(huán)境。 這個時候宙址,上下文已經(jīng)讀取yml文件了 所以這會兒引子中yml創(chuàng)建的參數(shù)轴脐,上下文讀到了配置信息,又有點小激動了抡砂!接著看大咱,向beanFactory注冊單例bean:一個參數(shù)bean,一個Bannerbean注益。
prepareContext() 這個方法大概先這樣碴巾,然后回到run方法中,看看項目啟動還干了什么丑搔。
refreshContext(context) 接著要刷新上下問環(huán)境了厦瓢,這個比較重要,也比較復(fù)雜啤月,今天只看個大概煮仇,有機(jī)會另外寫一篇博客,說說里面的東西谎仲,這里面主要是有個refresh()方法浙垫。看注釋可知强重,這里面進(jìn)行了Bean工廠的創(chuàng)建绞呈,激活各種BeanFactory處理器贸人,注冊BeanPostProcessor,初始化上下文環(huán)境佃声,國際化處理艺智,初始化上下文事件廣播器,將所有bean的監(jiān)聽器注冊到廣播器(這樣就可以做到Spring解耦后Bean的通訊了吧)
總之圾亏,Bean的初始化我們已經(jīng)做好了十拣,他們直接也可以很好的通訊。
接著回到run方法志鹃,
afterRefresh(context, applicationArguments); 這方法里面沒有任何東西夭问,網(wǎng)上查了一下,說這里是個拓展點曹铃,有機(jī)會研究下缰趋。
接著stopWatch.stop();啟動就算完成了,因為這邊啟動時間結(jié)束了陕见。
我正要失落的發(fā)現(xiàn)沒找到我們引子中說到的ApplicationRunner這個類,就在下面看到了最后一個方法秘血,必須貼出來源碼:
callRunners(context, applicationArguments),當(dāng)然這個方法前面還有l(wèi)isteners.started().
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
看到?jīng)]评甜,這會就執(zhí)行了ApplicationRunner 方法(至于CommandLineRunner灰粮,和ApplicationRunner類似,只是參數(shù)類型不同忍坷,這邊不做過多區(qū)分先)粘舟。所以可以說ApplicationRunner不是啟動的一部分,不記錄進(jìn)入SpringBoot啟動時間內(nèi)佩研,這也好理解啊柑肴,你自己初始化數(shù)據(jù)的時間憑什么算到我SpringBoot身上,你要初始化的時候做了個費時操作韧骗,回頭又說我SpringBoot辣雞嘉抒,那我不是虧得慌...
最后run下這個,listeners.running(context);這會兒用戶自定義的事情也會被調(diào)用了袍暴。
ok些侍,結(jié)束了。
小結(jié)
今天只是大概看了下SpringBoot啟動過程政模。有很多細(xì)節(jié)岗宣,比如refresh()都值得再仔細(xì)研究一下。SpringBoot之所以好用淋样,就是幫助我們做了很多配置耗式,省去很多細(xì)節(jié)(不得不說各種stater真實讓我們傻瓜式使用了很多東西),但是同樣定位bug或者通過項目聲明周期搞點事情的時候會無從下手。所以刊咳,看看SpringBoot源碼還是聽有必要的彪见。