本文从源码层面讲解Spring boot应用的启动流程。
Spring boot应用的入口通常是这样的形式
@SpringBootApplication(scanBasePackages = "com.sample")
public class SampleApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return configureApplication(builder);
}
public static void main(String[] args) {
configureApplication(new SpringApplicationBuilder()).run(args);
}
private static SpringApplicationBuilder configureApplication(SpringApplicationBuilder builder) {
return builder.sources(SampleApplication.class);
}
}
Spring构建SpringApplication实例使用了建造者模式。SpringApplicationBuilder里封装了构建Spring应用的细节。在实例化SpringApplicationBuilder时,会触发SpringApplication的初始化
org.springframework.boot.SpringApplication#initialize
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
// 推断是否是web环境,推断的依据是,如果classpath中同时存在以下两个类,则为web环境
// "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"
this.webEnvironment = deduceWebEnvironment();
// 从META-INF/spring.factories中获取所有ApplicationContextInitializer类名称,并通过反射一一实例化
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 加载所有的ApplicationListener监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断应用入口类,即包含main方法的类。如果是通过外置servlet容器启动,如tomcat,则入口类是tomcat的入口,如果通过内置tomcat启动,入口类即是我们的main方法所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
完成SpringApplication的初始化后,调用SpringApplicationBuilder的run方法,开始创建Spring的上下文,即IOC容器
org.springframework.boot.builder.SpringApplicationBuilder#run
public ConfigurableApplicationContext run(String... args) {
if (this.running.get()) {
// If already created we just return the existing context
return this.context;
}
// 如果存在父上下文,则创建父上下文的Initializer,维护到初始化器列表中,并将本上下文设置为子上下文
configureAsChildIfNecessary(args);
if (this.running.compareAndSet(false, true)) {
synchronized (this.running) {
// If not already running copy the sources over and then run.
// 将builder中指定到source传给application,并开始执行application的run方法
this.context = build().run(args);
}
}
return this.context;
}
org.springframework.boot.SpringApplication#run(java.lang.String...)
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
// 从spring.factories中获取SpringApplicationRunListeners,与ApplicationListener不同,它的生命周期只维持在上下文启动的过程中,监听这个过程的各个阶段,而ApplicationListener则与应用共存,用于监听应用中产生的各种事件
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 准备环境,环境用于维护属性资源。在准备environment阶段,EventPublishingRunListener会广播EnvironmentPreparedEvent事件
// 在监听了该事件的监听器中包括ConfigFileApplicationListener,该监听器会获取spring.config.location指向的属性资源,添加到environment中
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 打印横幅,也就是Spring应用启动时我们在控制台能看到的由符号拼接起来的Spring logo
Banner printedBanner = printBanner(environment);
// 创建上下文对象,如果是web环境,则创建的是AnnotationConfigEmbeddedWebApplicationContext对象
// 否则创建的是AnnotationConfigApplicationContext对象
// 在无参构造器中会创建AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner实例,分别用于注解bean定义的解析和类路径中bean定义的扫描
context = createApplicationContext();
// 加载失败分析器,用于为用户提供有价值的错误分析信息
analyzers = new FailureAnalyzers(context);
// 准备上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 刷新上下文
refreshContext(context);
// 刷新后的收尾工作
afterRefresh(context, applicationArguments);
// 调用SpringApplicationRunListener的finished方法
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
环境准备阶段结束后,listeners中的EventPublishingRunListener会广播ApplicationEnvironmentPreparedEvent事件,支持该事件的ApplicationListener将会处理,其中包括BootstrapApplicationListener,BootstrapApplicationListener会在事件响应中创建bootstrap上下文,并将其设置为应用上下文的父上下文,BootstrapApplicationListener还会往上下文中添加自己的Initializer,包括配置文件加解密Initializer。
接下来我们详细介绍prepareContext、refreshContext都做了些啥。
org.springframework.boot.SpringApplication#prepareContext
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 将创建的environment装进上下文,此时的environment依然不包括用户配置
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 调用所有初始化器的initialize方法,bootstrap上下文对应的初始化器此时会将bootstrap上下文设置为应用上下文的parent
// spring cloud的属性文件解密Initializer会对加密配置项进行解密
applyInitializers(context);
// 调用所有SpringApplicationRunListener的contextPrepared方法
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 将applicationArguments注册为单例bean
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
// 将横幅打印器注册为单例bean
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 这一步实际上是将入口类注册为bean
load(context, sources.toArray(new Object[sources.size()]));
// 调用所有SpringApplicationRunListener的contextLoaded方法
// EventPublishingRunListener广播ApplicationPreparedEvent,监听了该事件的ConfigFileApplicationListener会往上下文里设置一个PropertySourceOrderingPostProcessor
listeners.contextLoaded(context);
}
org.springframework.context.support.AbstractApplicationContext#refresh
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 主要添加Servlet上下文启动参数到propertysources中
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 准备工作,设置默认的后置bean处理器等
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// 激活BeanFactory后置处理器
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// 注册所有的bean后置处理器,用于在bean创建后做一些定制
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// 从bean工厂中获取国际化资源,并设置父国际化资源
// Initialize message source for this context.
initMessageSource();
// 设置Application事件广播员
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
// 创建内嵌servlet容器,并初始化servlet属性资源
onRefresh();
// Check for listener beans and register them.
// 向上面注册的SimpleApplicationEventMulticaster中注册ApplicationListener
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
// 实例化所有非懒加载的单例bean
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
// 发布ContextRefreshedEvent,启动servlet容器
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
以下是Spring boot启动的流程图