在Spring Boot的入口類中顽耳,我們通常是通過調(diào)用SpringApplication的run方法來啟動(dòng)Spring Boot項(xiàng)目。這節(jié)我們來深入學(xué)習(xí)下SpringApplication的一些細(xì)節(jié)奈梳。
- 自定義SpringApplication
1.1 通過SpringApplication API 調(diào)整
1.2 通過SpringApplicationBuilder API調(diào)整 - SpringApplication準(zhǔn)備階段
2.1 配置源
2.2 推斷應(yīng)用類型
2.3 加載應(yīng)用上下文初始器
2.4 加載應(yīng)用事件監(jiān)聽器
2.5 推斷入口類 - SpringApplication運(yùn)行階段
3.1 開啟事件監(jiān)聽
3.2 開啟運(yùn)行監(jiān)聽器
3.3 創(chuàng)建Environment
3.4 是否打印Banner
3.5 創(chuàng)建Context
3.6 裝配Context
3.7 Refresh Context
3.8 廣播應(yīng)用已開啟
3.9 執(zhí)行Runner
3.10 廣播應(yīng)用運(yùn)行中
自定義SpringApplication
默認(rèn)的我們都是直接通過SpringApplication的run方法來直接啟動(dòng)Spring Boot,其實(shí)我們可以通過一些API來調(diào)整某些行為解虱。
通過SpringApplication API調(diào)整
創(chuàng)建一個(gè)SpringBoot項(xiàng)目攘须,添加web 依賴。
然后將入口類代碼修改為:
SpringApplication application = new SpringApplication(Application.class);
application.setBannerMode(Banner.Mode.OFF);
application.setWebApplicationType(WebApplicationType.NONE);
application.setAdditionalProfiles("dev");
application.run(args);
通過調(diào)用SpringApplication的方法殴泰,我們關(guān)閉了Banner的打印于宙,設(shè)置應(yīng)用環(huán)境為非WEB應(yīng)用,profiles指定為dev悍汛。除此之外捞魁,SpringApplication還包含了許多別的方法,具體可以查看源碼或者官方文檔:
通過SpringApplicationBuilder API調(diào)整
SpringApplicationBuilder提供了Fluent API离咐,可以實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用谱俭,下面的代碼和上面的效果一致,但在編寫上較為方便:
new SpringApplicationBuilder(Application.class)
.bannerMode(Banner.Mode.OFF)
.web(WebApplicationType.NONE)
.profiles("dev")
.run(args);
SpringApplication準(zhǔn)備階段
SpringApplication的生命周期階段大致可以分為準(zhǔn)備階段和運(yùn)行階段宵蛀。
我們通過源碼來查看SpringApplication的有參構(gòu)造器:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
通過有參構(gòu)造器里的代碼我們可以將SpringApplication的準(zhǔn)備階段分為以下幾個(gè)步驟:
配置源
構(gòu)造器中this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));這行代碼用于加載我們配置的Spring Boot Bean源昆著。通常我們使用SpringApplication或者SpringApplicationBuilder的構(gòu)造器來直接指定源。
所謂的Spring Boot Bean源指的是某個(gè)被@SpringBootApplication注解標(biāo)注的類术陶,比如入口類:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.run(args);
}
我們也可以將上面的代碼改為下面這種方式:
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(ApplicationResource.class);
application.run(args);
}
@SpringBootApplication
public static class ApplicationResource {
}
}
這樣也是可行的凑懂。查看SpringApplication的單個(gè)參數(shù)構(gòu)造器:
public SpringApplication(Class... primarySources) {
this((ResourceLoader)null, primarySources);
}
說明我們除了配置單個(gè)源外,還可以配置多個(gè)源瞳别。
推斷應(yīng)用類型
構(gòu)造器中這行this.webApplicationType = WebApplicationType.deduceFromClasspath();代碼用于推斷當(dāng)前Spring Boot應(yīng)用類型
Spring Boot 2.0后征候,應(yīng)用可以分為下面三種類型:
- WebApplicationType.NONE:非WEB類型;
- WebApplicationType.REACTIVE:Web Reactive類型祟敛;
- WebApplicationType.SERVLET:Web Servlet類型疤坝。
WebApplicationType.deduceFromClasspath()或根據(jù)當(dāng)前應(yīng)用ClassPath中是否存在相關(guān)的實(shí)現(xiàn)類來判斷應(yīng)用類型到底是哪個(gè),deduceFromClasspath方法的源碼如下所示:
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
return REACTIVE;
} else {
String[] var0 = SERVLET_INDICATOR_CLASSES;
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
String className = var0[var2];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return NONE;
}
}
return SERVLET;
}
}
我們也可以直接通過SpringApplication的setWebApplicationType方法或者SpringApplicationBuilder的web方法來指定當(dāng)前應(yīng)用的類型馆铁。
加載應(yīng)用上下文初始器
接著下一行代碼setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));用于加載應(yīng)用上下文初始器ApplicationContextInitializer跑揉。
getSpringFactoriesInstances方法的源碼如下所示:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return this.getSpringFactoriesInstances(type, new Class[0]);
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
上面代碼利用Spring工廠加載機(jī)制,實(shí)例化ApplicationContextInitializer實(shí)現(xiàn)類埠巨,并進(jìn)行排序历谍。
所以我們可以通過實(shí)現(xiàn)ApplicationContextInitializer接口用于在Spring Boot應(yīng)用初始化之前執(zhí)行一些自定義操作。
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("ConfigurableApplicationContext.id -" + configurableApplicationContext.getId());
}
}
上面代碼中實(shí)現(xiàn)了initialize方法辣垒,并且使用@Order注解指定優(yōu)先級望侈。其中Ordered.HIGHEST_PRECEDENCE等于Integer.MIN_VALUE,Ordered.LOWEST_PRECEDENCE等于Integer.MAX_VALUE勋桶。所以數(shù)值越小脱衙,優(yōu)先級越高侥猬。
除了使用@Order注解來指定優(yōu)先級外,我們也可以通過實(shí)現(xiàn)org.springframework.core.Ordered接口的getOrder方法來指定優(yōu)先級捐韩。
public class AfterHelloApplicationContextInitializer implements ApplicationContextInitializer ,Ordered{
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println("AfterConfigurableApplicationContext.id -" + configurableApplicationContext.getId());
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
上面通過getOrder方法來指定了優(yōu)先級為最低優(yōu)先級退唠。
創(chuàng)建好后,我們還需在工廠配置文件里配置這兩個(gè)實(shí)現(xiàn)類荤胁。在resources目錄下新建META-INF目錄瞧预,并創(chuàng)建spring.factories文件:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
top.lconcise.config.HelloApplicationContextInitializer,\
top.lconcise.config.AfterHelloApplicationContextInitializer
加載應(yīng)用事件監(jiān)聽器
在加載完應(yīng)用上下文初始器后,下一行的setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));代碼加載了應(yīng)用事件監(jiān)聽器仅政。與加載事件上下文初始器類似垢油,Spring Boot也是通過Spring的工廠方法來實(shí)例化ApplicationListener的實(shí)現(xiàn)類,并進(jìn)行排序已旧。
既然是事件監(jiān)聽秸苗,那么其可以監(jiān)聽什么事件呢?其監(jiān)聽的是ApplicationEvent接口的實(shí)現(xiàn)類运褪,我們查看一下都有哪些事件實(shí)現(xiàn)了這個(gè)接口:
這里我們以ContextClosedEvent為例子來編寫自定義的應(yīng)用事件監(jiān)聽器,監(jiān)聽Spring上下文關(guān)閉事件玖瘸。
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContextClosedEventListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
System.out.println("ContextClosedEvent: " + contextClosedEvent.getApplicationContext().getId());
}
}
public class AfterContextClosedEventListener implements ApplicationListener<ContextClosedEvent>, Ordered {
@Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
System.out.println("AfterContextClosedEvent: " + contextClosedEvent.getApplicationContext().getId());
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
# Application Listeners
org.springframework.context.ApplicationListener=\
top.lconcise.listener.ContextClosedEventListener,\
top.lconcise.listener.AfterContextClosedEventListener
推斷入口類
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
StackTraceElement[] var2 = stackTrace;
int var3 = stackTrace.length;
for(int var4 = 0; var4 < var3; ++var4) {
StackTraceElement stackTraceElement = var2[var4];
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException var6) {
;
}
return null;
}
SpringApplication運(yùn)行階段
SpringApplication的運(yùn)行階段對應(yīng)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);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
運(yùn)行階段大致可以分為下面這幾個(gè)過程:
開啟時(shí)間監(jiān)聽
StopWatch stopWatch = new StopWatch();
stopWatch.start();
上面代碼用于開啟Spring Boot應(yīng)用啟動(dòng)時(shí)間監(jiān)聽,配合下面的stopWatch.stop();便可以計(jì)算出完整的啟動(dòng)時(shí)間雅倒。
開啟運(yùn)行監(jiān)聽器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
getRunListeners方法源碼:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
上面代碼通過SpringFactoriesLoader檢索META-INF/spring.factories找到聲明的所有SpringApplicationRunListener的實(shí)現(xiàn)類并將其實(shí)例化璃诀,然后裝配到List<SpringApplicationRunListener>運(yùn)行監(jiān)聽器集合中。
listeners.started();用于遍歷運(yùn)行監(jiān)聽器集合中的所有SpringApplicationRunListener的實(shí)現(xiàn)類蔑匣,并逐一調(diào)用它們的starting方法劣欢,廣播Spring Boot應(yīng)用要開始啟動(dòng)了。
在Spring Boot中SpringApplicationRunListener接口用于監(jiān)聽整個(gè)Spring Boot應(yīng)用生命周期裁良,其代碼如下所示:
public interface SpringApplicationRunListener {
void starting();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void started(ConfigurableApplicationContext context);
void running(ConfigurableApplicationContext context);
void failed(ConfigurableApplicationContext context, Throwable exception);
}
創(chuàng)建 Environment
run方法中的這行代碼用于創(chuàng)建并配置當(dāng)前SpringBoot應(yīng)用將要使用的Environment(包括配置要使用的PropertySource以及Profile):
onfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
我們已經(jīng)在準(zhǔn)備階段里推斷出了應(yīng)用類型凿将,這里只要根據(jù)相應(yīng)的應(yīng)用類型來創(chuàng)建相應(yīng)的應(yīng)用環(huán)境即可,類型和環(huán)境對應(yīng)關(guān)系如下:
- Web Reactive: StandardReactiveWebEnvironment
- Web Servlet: StandardServletEnvironment
- 非 Web: StandardEnvironment
在prepareEnvironment方法中會(huì)執(zhí)行l(wèi)isteners.environmentPrepared(environment);价脾,用于遍歷調(diào)用所有SpringApplicationRunListener實(shí)現(xiàn)類的environmentPrepared()方法牧抵,廣播Environment準(zhǔn)備完畢。
是否打印Banner
run方法中的這行代碼會(huì)根據(jù)我們的配置來決定是否打印Banner:
Banner printedBanner = this.printBanner(environment);
創(chuàng)建Context
run方法中的這行代碼用于創(chuàng)建ApplicationContext
context = this.createApplicationContext();
不同的環(huán)境對應(yīng)不同的ApplicationContext:
- Web Reactive: AnnotationConfigReactiveWebServerApplicationContext
- Web Servlet: AnnotationConfigServletWebServerApplicationContext
- 非 Web: AnnotationConfigApplicationContext
裝配Context
run方法中的這行代碼用于裝配Context:
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
方法prepareContext的源碼如下所示:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
this.applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
prepareContext方法開頭為ApplicationContext加載了environment侨把,之后通過applyInitializers方法逐個(gè)執(zhí)行ApplicationContextInitializer的initialize方法來進(jìn)一步封裝ApplicationContext犀变,并調(diào)用所有的SpringApplicationRunListener實(shí)現(xiàn)類的contextPrepared方法,廣播ApplicationContext已經(jīng)準(zhǔn)備完畢了秋柄。
之后初始化IOC容器获枝,并調(diào)用SpringApplicationRunListener實(shí)現(xiàn)類的contextLoaded方法,廣播ApplicationContext加載完成骇笔,這里就包括通過@EnableAutoConfiguration導(dǎo)入的各種自動(dòng)配置類省店。
Refresh Context
run方法中的這行代碼用于初始化所有自動(dòng)配置類机隙,并調(diào)用ApplicationContext的refresh方法:
this.refreshContext(context);
廣播應(yīng)用已啟動(dòng)
run方法中的這行代碼用于廣播Spring Boot應(yīng)用已啟動(dòng):
listeners.started(context);
started方法會(huì)調(diào)用所有的SpringApplicationRunListener的finished方法,廣播SpringBoot應(yīng)用已經(jīng)成功啟動(dòng)萨西。
執(zhí)行Runner
run方法中的這行代碼callRunners(context, applicationArguments);遍歷所有ApplicationRunner和CommandLineRunner的實(shí)現(xiàn)類有鹿,并執(zhí)行其run方法。我們可以實(shí)現(xiàn)自己的ApplicationRunner或者CommandLineRunner谎脯,來對Spring Boot的啟動(dòng)過程進(jìn)行擴(kuò)展葱跋。
@Component
public class HelloApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner : Hello SpringBoot ");
}
}
這里我們需要將HelloApplicationRunner使用@Component注解標(biāo)注,讓其注冊到IOC容器中源梭。
@Component
public class HelloCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner Hello SpringBoot ");
}
}
程序運(yùn)行輸出如下:
ApplicationRunner : Hello SpringBoot
CommandLineRunner : Hello SpringBoot
廣播應(yīng)用運(yùn)行中
run方法中的這行代碼listeners.running(context);用于調(diào)用SpringApplicationRunListener的running方法娱俺,廣播Spring Boot應(yīng)用正在運(yùn)行中。
當(dāng)run方法運(yùn)行出現(xiàn)異常時(shí)废麻,便會(huì)調(diào)用handleRunFailure方法來處理異常荠卷,該方法里會(huì)通過listeners.failed(context, exception);來調(diào)用SpringApplicationRunListener的failed方法,廣播應(yīng)用啟動(dòng)失敗烛愧,并將異常擴(kuò)散出去油宜。
上面所有的廣播事件都是使用Spring的應(yīng)用事件廣播器接口ApplicationEventMulticaster的實(shí)現(xiàn)類SimpleApplicationEventMulticaster來進(jìn)行廣播的。
源碼地址:https://github.com/lbshold/springboot/tree/master/Spring-Boot-SpringApplication
參考文章