背景知识
无论是分析SpringApplication.run方法还是分析SpringBoot自动配置原理或者其它方面,有一个配置文件始终绕不开,那就是META-INF目录下的spring.factories文件,SpringBoot的自动配置原理就是基于该文件。在该文件中配置了大量的Key-Value,如果需要扩展SpringBoot,可以参考这种写法。
源码分析
通常我们启动一个SpringBoot应用,在启动类上添加一个@SpringBootApplication注解,然后写一个main方法,在该方法内部执行SpringApplication.run,传入启动类的class以及main方法的参数。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
首先我们从SpringApplication的run方法开始分析,在该方法中调用重载的run方法。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
在重载的run方法中,创建了一个SpringApplication实例,并调用重载run方法。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
在SpringApplication构造函数中调用了重载的构造函数。
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
在重载的构造函数中,完成了当前环境的推断以及ApplicationContextInitializer实现类实例化以及ApplicationListener实现类的实例化等。
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();
// ApplicationContextInitializer实现类实例化
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// ApplicationListener实现类实例化
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断main方法所在类
this.mainApplicationClass = deduceMainApplicationClass();
}
也许大家比较好奇SpringBoot是如何推断当前运行环境,这里我们就先分析这个推断运行环境逻辑。
其实推断逻辑非常简单,就是判断某些类存不存在类路径下。
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;
}
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
明白SpringBoot是如何推断当前运行环境后,接下来我们就分析SpringBoot的核心方法-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); // 获取classpath:META-INF/spring.factories文件中key为:org.springframework.boot.SpringApplicationRunListener的value 并进行实例化
listeners.starting(); // 调用所有SpringApplicationRunListener实现类的starting方法
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 预刷新环境
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment); // 打印Banner
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); // 调用所有SpringApplicationRunListener实现类的started方法
callRunners(context, applicationArguments); // 调用所有ApplicationRunner和CommandLineRunner的run方法
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);// 调用所有SpringApplicationRunListener实现类的running方法
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}// 返回应用上下文
return context;
}
getRunListeners
首先来分析getRunListeners方法,在该方法中,通过getSpringFactoriesInstances方法来获取classpath下META-INF/spring.factories文件中key为org.springframework.boot.SpringApplicationRunListener的value,然后并进行实例化,将结果作为参数传入到SpringApplicationRunListeners的构造函数中,最后返回该实例。
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
在SpringBoot工程中该值只有一个,即EventPublishingRunListener。在SpringBoot自动配置模块-SpringBootAutoConfigure工程中并未定义。
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
接下来就是调用SpringApplicationRunListeners的starting方法,在该方法中循环调用所有SpringApplicationRunListener 实现类的starting方法,通过前面分析我们可以得知默认情况下只有一个EventPublishingRunListener。
// SpringApplicationRunListeners#starting
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
在EventPublishingRunListener的starting方法中,调用了Spring 应用事件广播器的multicastEvent方法来发布ApplicationStartingEvent事件。注意这个SimpleApplicationEventMulticaster是Spring的应用上下文事件广播器,并且Spring应用上下文是会去创建一个事件广播器,但并不是在这里,这还属于SpringBoot范围,并且Spring标准事件中并没有ApplicationStartingEvent事件,这个事件属于SpringBoot扩展出来的应用事件。
private final SimpleApplicationEventMulticaster initialMulticaster;
// EventPublishingRunListener#starting
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
prepareEnvironment
创建DefaultApplicationArguments实例这里就不再分析了,无非就是对参数的封装。接下来就是prepareEnvironment方法。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 首先根据当前环境来创建对应的Environment实例,例如当前是Servlet环境或者Reactive或者普通环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置刚创建出来的环境,主要是设置类型转换器,将命令行中的配置保存到Environment中
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 将前面配置进Environment中属性全部复制一份来再进行保存
ConfigurationPropertySources.attach(environment);
// 发布ApplicationEnvironmentPreparedEvent事件
listeners.environmentPrepared(environment);
// 绑定Environment到SpringApplication中
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
// 添加一个环境转换器
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
// 将前面配置进Environment中属性全部复制一份来再进行保存
ConfigurationPropertySources.attach(environment);
return environment;
}
configureIgnoreBeanInfo方法就是配置要忽略的BeanInfo信息,以及printBanner方法用来打印Banner,这里就不展开分析了。
createApplicationContext
createApplicationContext方法决定了SpringBoot使用何种应用上下文,如果是Servlet环境就使用AnnotationConfigServletWebServerApplication,如果是Reactive就使用AnnotationConfigReactiveWebServerApplicationContext,否则使用AnnotationConfigApplicationContext。最后使用反射来进行实例化。
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
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);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
prepareContext
创建完应用上下文之后,接下来便是预刷新应用上下文了-prepareContext,
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment); // 将前面并配置好的Environement设置到应用上下文
postProcessApplicationContext(context); // 后置处理应用上下文
applyInitializers(context); // 调用所有ApplicationContextInitializer实现类的initialize方法
listeners.contextPrepared(context); // 发布ApplicationContextInitializedEvent事件
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null); // 打印启动详情
logStartupProfileInfo(context); // 打印当前环境(Profile)
}
// 将前面创建的DefaultApplicationArguments作为单例Bean注册到BeanFactory中
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {// 将PrintBanner作为单例Bean注册到BeanFactory中
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
// 将是否允许BeanDefinition覆盖设置为false,Spring默认是允许的!!!
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {// 注册一个BeanFactoryPostProcessor
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// 加载启动源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 为main方法所在类创建BeanDefinition,并注册进Spring应用上下文
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);// 发布ApplicationPreparedEvent事件
}
refreshContext
在refreshContext方法中首先调用应用上下文的refresh方法来刷新应用上下文(调用应用上下文的refresh方法),接下来调用registerShutdownHook方法注册一个钩子。
private void refreshContext(ConfigurableApplicationContext context) {
refresh((ApplicationContext) context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
afterRefresh
afterRefresh方法目前只是一个空实现,预留扩展方法。
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
callRunners
接下来就是调用秒表的stop方法结束计时(stopWatch.stop()),然后打印启动时间(秒数)。发布ApplicationStartedEvent事件以及AvailabilityChangeEvent事件(listeners.started(context))。这里就不再展开分析。
在callRunners方法中,通过应用上下文来获取所有ApplicationRunner以及CommandLineRunner接口实现类,接下来逐个调用其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);
}
}
}
最后,发布ApplicationReadyEvent以及AvailabilityChangeEvent事件(listeners.running(context))。