前言
经历过java面试的小伙伴,大部分都会被问到过SpringBoot启动流程类型的问题。这作为我们深入了解SpringBoot框架来说,是一门必修课。本博文会在源码的基础上记录一下SpringBoot的启动流程。
SpringBoot的启动流程
我们点进SpringApplication.run(Class[] primarySources, String[] args)
这个方法会发现,该方法分为两个部分:初始化对象和执行该对象的run
方法,如图所示:
下面呢,我们分两个阶段来讲解启动流程。
初始化阶段
我们点进SpringBootApplication
的构造器,如图所示:
这里,我们需要重点关注方法:getSpringFactoriesInstances
。该方法会在启动过程中多次用到,我们看一下方法体,如下图所示:
总结一下,在类的初始化阶段,主要是做的动作主要有:设置启动类型(分为Application项目、Web项目和Reactive项目)、设置容器的初始化器、设置容器的监听器和设置容器的启动类。这里需要注意的一点是:我们的项目配置文件是在监听器初始化过程中加载的,也就是说容器初始化完成,我们项目的配置文件已经加载完毕了。
启动阶段
启动阶段步骤较多,我把源码贴在下面:
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
// 初始化一个计时器并开启
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置headless环境变量
configureHeadlessProperty();
// 封装所有的监听器并全部启动
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 封装启动参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 配置我们的环境上下文
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置需要忽略的bean信息
configureIgnoreBeanInfo(environment);
// 打印Banner
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);
// 触发自定义的ApplicationRunner或者CommandLineRunner的实现类的组件列表
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;
}
上文的代码,我已经将每一步都加了说明文案。其实步骤比较清晰,下面我们针对比较重要的步骤来做进一步的讲解。
首先,我们看一下容器环境的准备步骤,如下图所示:
这里的话,比较重要的就是根据环境创建环境对象。这里跟容器初始化的判断逻辑一致,就是判断当前环境是一个普通的应用环境还是Web环境。
环境准备就绪之后,我们再来关注一下我们应用上下文的创建步骤。如下图所示:
这里我们可以看到很熟悉的逻辑,根据当前环境来选择反射创建不同的上下文实例。由于我们目前来讲Web环境应用的比较多,我们就按照Web环境来走一下接下来的步骤。Web环境下创建的上下文示例为org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
。
创建好实例之后,我们看一下准备阶段的步骤:
准备好容器之后,就进入了我们最重要的容器刷新阶段:
我们点进refresh
方法:
这里我们可以看到,调用的是AbstractApplicationContext
的refresh
方法,该方法是我们学习Spring家族框架时最重要也是最核心的方法。我们点开该方法:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备刷新,主要是设置刷新标识,校验必要参数等等准备工作
prepareRefresh();
// 获取内部容器实例,该方法包含刷新内容容器并返回两个步骤,均为抽象方法
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备内部容器,主要是注册一写初始化的bean以及设置各种属性
prepareBeanFactory(beanFactory);
try {
// 向上下文中添加了一系列的Bean的后置处理器
postProcessBeanFactory(beanFactory);
// 实例化并调用所有的BeanFactoryPostProcessor,此步骤很重要,我们的自动装配就在此步骤内实现的
invokeBeanFactoryPostProcessors(beanFactory);
// 注册所有的Bean的后置处理器
registerBeanPostProcessors(beanFactory);
// 初始化信息源
initMessageSource();
// 初始化容器时间广播器
initApplicationEventMulticaster();
// 钩子函数
onRefresh();
// 注册监听器
registerListeners();
// 实例化所有的非懒加载的bean
finishBeanFactoryInitialization(beanFactory);
// 刷新完成函数
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();
}
}
}
其中,invokeBeanFactoryPostProcessors
函数是重点。该函数会实例化所有的 BeanFactoryPostProcessor
并调用其postProcessBeanFactory
方法来向容器中自定义注册一些bean的定义信息。这里就会调用到一个非常重要的后置处理器ConfigurationClassPostProcessor
。这个后置处理器会处理所有的带有@Configuration
注解的类并解析其中自定义的@Bean
注解注入的bean。除此之外,他还会查看是否该类上标有@ComponentScan
注解,有的话会扫描基础路径下所有的带有@Component
(以及子注解)的bean。最最重要的是,他还会扫描该类上的@Import
注解扫描对应类的方法来注册自定义的bean(也就是我们讲的自动装配的原理,这里就会加载定义的自动配置的类并筛选排序后进行注入容器)。
后面就是一些后续流程,有兴趣的可以继续研究一下。
总结
我们总结一下,SpringBoot的启动流程分为两个阶段:初始化以及运行阶段。初始化阶段主要是设置一些容器初始化起和监听器以及一些属性,运行阶段主要是初始化并配置环境、初始化容器、配置并刷新容器和发布一系列的事件。
文章评论