项目中我们通过SpringApplication.run启动SpringBoot应用,但却不知内部的逻辑,与Spring的整合,因此本文将从源码出发,对于SpringApplication的启动流程做简要阐述。
一般使用springboot项目,都是这样启动的,SpringApplication.run(xxx),本文将会分两部分处理:SpringApplication配置阶段,称为SpringBoot初始化。run方法逻辑,称为SpringBoot启动。
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
1. SpringBoot初始化
内部实际的逻辑是SpringApplication然后调用run方法。
//SpringApplication
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
//SpringApplication
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
这提供了启发,如果需要对SpringApplication进行配置的话,可以通过new SpringApplication的方式,进行设置。比如关闭Banner打印可以这样启动。
SpringApplication springApplication = new SpringApplication(MyApplication.class);
springApplication.setBannerMode(Banner.Mode.OFF);
springApplication.run(args);
同时Spring也提供了Builder模式,方便通过budiler进行构建SpringApplication,例如:
new SpringApplicationBuilder(MyApplication.class)
.bannerMode(Banner.Mode.OFF)
.run(args);
这里对SpringApplication的配置,会在SpringApplication运行的时候,运用起来。现在先看下new SpringApplication内部初始化了哪些内容。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//解析web类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//判断mian函数所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
web类型,主要通过判断对应的class是否存在,默认有三种类型,REACTIVE、SERVLET和NONE。比如SERVLET就会判断javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext是否存在(通过类加载器尝试加载,通过是否会报错判断)。
//WebApplicationType#deduceFromClasspath
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
获取初始化器和监听器逻辑类似,都是从spring.factories文件中获取对应的类,然后通过反射进行实例化,最后在进行排序(如根据Ordered接口)。把获取到的集合,设置到SpringApplication的属性中
注意,此时实例的类还没有加入到Spring IOC容器。
//SpringApplication#getSpringFactoriesInstances
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
//SpringApplication#getSpringFactoriesInstances
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
//SpringFactoriesLoader.loadFactoryNames内部会从spring.factories文件中进行加载对应类型的类。
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//进行实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//进行排序,如设置了@Order之类的注解,或者实现了Ordered接口的情况
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
而判断main函数所在的类,是通过运行时的堆栈信息中,查看main方法所在的类,然后通过反射进行获取。
//SpringApplication#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;
}
2. SpringBoot启动流程
去掉SpringBoot启动无关的代码后,启动逻辑如下所示:获取获取SpringApplicationRunListener、Environment准备、Banner打印、准备SpringBoot上下文、启动Spring上下文、ApplicationRunner执行等逻辑,会根据启动的阶段,将启动流程分为三部分:
- 启动前准备阶段
- 启动阶段
- 启动后阶段
//SpringApplication#run
public ConfigurableApplicationContext run(String... args) {
//获取SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//environment准备
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
//Baner打印
Banner printedBanner = printBanner(environment);
//创建ApplicationContext
context = createApplicationContext();
//获取SpringBootExceptionReporter对象
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//启动Spring上下文
refreshContext(context);
//启动后逻辑
afterRefresh(context, applicationArguments);
//日志打印
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
//ApplicationRunner执行
callRunners(context, applicationArguments);
}catch (Throwable ex) {
//异常处理
handleRunFailure(context, ex, exceptionReporters, listeners);
}
//ApplicationListener执行
listeners.running(context);
return context;
}
2.1 准备阶段
2.1.1 获取SpringApplicationRunListener
底层的实现逻辑,是从spring.factories文件中获取SpringApplicationRunListener的对象,然后封装成SpringApplicationRunListeners,SpringApplicationRunListeners的逻辑其实就是内部有多个SpringApplicationRunListener,然后执行SpringApplicationRunListeners方法的时候,内部实际上就是循环调用SpringApplicationRunListener的方法,这种处理方法,在Spring中比较场景的就是xxxComposite,比如:WebMvcConfigurerComposite内部实际上包含WebMvcConfigurer的集合。
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
在Spring内置的SpringApplicationRunListener,只有EventPublishingRunListener。EventPublishingRunListener会在整个Spring启动阶段针对不同事件进行发送,比如:ApplicationStartingEvent、ApplicationReadyEvent等事件,内部实际上依赖的是Spring提供的SimpleApplicationEventMulticaster类。
Spring 事件可以参考本人的另外一篇文章:浅析Spring事件
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
在EventPublishingRunListener内,事件的处理会分为两步分,Spring ApplicationContext创建前、创建后。
- 在Spring ApplicationContext创建前,会通过自己实例化的SimpleApplicationEventMulticaster进行事件发布,此时Spring IOC容器还没有启动。所以ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent、ApplicationContextInitializedEvent和ApplicationPreparedEvent事件的监听,需要手动通过SpringApplication进行添加,或者通过spring.factories文件进行添加。
- 在Spring ApplicationContext创建后,会通过Spring IOC容器进行事件的发布。此类的事件有:ApplicationStartedEvent、ApplicationReadyEvent。此类事件监听器,可以直接通过Spring Bean进行处理。
这个前后的节点,就在SpringApplicationRunListener的contextLoaded方法中,会把SpringApplication中的监听器都加入到ApplicaitonContext中,然后再发布事件,此后的事件就会通过ApplicaitonContext进行发布了。
//EventPublishingRunListener#contextLoaded
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
2.1.2 准备Environment
Environment不打算深入,里面的内容也比较多,这里我们需要知道会准备Environment即可。内部的逻辑会先创建ConfigurableEnvironment,getOrCreateEnvironment内部的逻辑会根据web类型进行判断:
- 如果是SERVLET,则创建StandardServletEnvironment
- 如果是REACTIVE,则创建StandardReactiveWebEnvironment
- 其他,则创建StandardEnvironment
创建完后会进行配置,同时也会用SpringApplicationRunListener调用environmentPrepared方法。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//创建ConfigurableEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置ConfigurableEnvironment
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
//SpringApplicationRunListener处理
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
2.1.3 Banner打印
这是个无关紧要的功能,所以简单介绍一下,内部的逻辑会找图片Banner,或者文本Banner
- 图片Banner,可以通过spring.banner.image.location指定位置;还会从ResourceLoader中分别尝试获取banner.gif、banner.jpg、banner.png是否有图片;
- 文本Banner,可以通过spring.banner.location指定位置;如果没有配置的话,会从ResourceLoader中尝试加载banner.txt。
如果以上都找不到,并且SpringApplication没有设置Banner的情况下,会用Spring内置的Banner,也就是启动会打印SpringBoot的文本。
2.1.4 创建ApplicationContext
会根据web类型创建ApplicationContext
- Servlet:AnnotationConfigServletWebServerApplicationContext
- Reactive:AnnotationConfigReactiveWebServerApplicationContext
- 其他:AnnotationConfigApplicationContext
//SpringApplicaiotn#createApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
2.1.5 准备ApplicationContext
会对ApplicationContext设置Environment,然后会处理一下ApplicationContext内部逻辑就是设置了一下属性,如注册BeanNameGenerator到Spring IOC容器,设置resourceLoader之类。接着会调用ApplicationContextInitializer的initialize接口。调用SpringApplicationRunListener的contextPrepared方法;最后会进行日志打印,如:The following profiles are active的日志。
//SpringApplication#prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
//设置ApplicationContext
postProcessApplicationContext(context);
//调用ApplicationContextInitializer的initialize
applyInitializers(context);
//调用SpringApplicationRunListener的contextPrepared
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
//...
}
接着会在注册ApplicationArguments和Banner到Spring IOC容器中,会设置allowBeanDefinitionOverriding值到IOC容器。如果是延迟初始化,还会增加LazyInitializationBeanFactoryPostProcessor到ApplicationContext中。
//SpringApplication#prepareContext
// 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);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
最后会加载Source,然后调用SpringApplicationRunListener的contextLoaded方法
//SpringApplication#prepareContext
// 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);
2.2 启动
默认情况下,会调用ApplicationContext注册一个ShutdownHook,紧接着就是调用applicationContext的refresh方法,该方法会启动Spring的IOC容器。
ApplicationContext的启动流程,可以查看本人的另外一篇文章:浅析Spring启动流程
//SpringApplication#refreshContext
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
refresh(context);
}
//SpringApplication#refresh
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
2.3 启动后
启动会调用afterRefresh,这是一个空实现,是个模版方法,子类可以根据需要进行实现。紧接着会进行日志打印,类似Started xxx in xxx seconds的日志。然后会调用SpringApplicationRunListener的started方法;然后会调用callRunners方法。
callRunners内部的逻辑就是获取实现ApplicationRunner、CommandLineRunner接口的Bean,然后分别调用。这两个的接口类似,只是run方法的参数不同。
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);
}
}
}
最后会调用SpringApplicationRunListener的running方法,根据前面的描述,实际上就是会发送ApplicationReadyEvent的事件。
通过以上分析可以得知,如果我们某个Bean的逻辑要在SpringBoot启动完在处理,可以有以下几种方法处理:
- 实现ApplicationRunner接口
- 实现CommandLineRunner接口
- 监听ApplicationReadyEvent事件。
3. 参考资料
- Spring boot源码2.2.x分支
- 《SpringBoot编程思想(核心篇)》