首页 > 其他分享 >SpringBoot启动流程大揭秘

SpringBoot启动流程大揭秘

时间:2022-12-09 14:04:42浏览次数:29  
标签:run SpringBoot 启动 流程 监听器 SpringApplication environment context 揭秘


SpringBoot启动流程大揭秘

  • ​​什么是SpringBoot​​
  • ​​SpringBoot启动流程​​
  • ​​SpringApplication.run​​
  • ​​SpringApplication初始化​​
  • ​​WebApplicationType​​
  • ​​加载spring.factories​​
  • ​​deduceMainApplicationClass​​
  • ​​自定义spring.factories​​
  • ​​SpringBoot启动run​​
  • ​​createBootstrapContext​​
  • ​​getRunListeners​​
  • ​​prepareEnvironment​​
  • ​​printBanner​​
  • ​​prepareContext​​
  • ​​refreshContext​​
  • ​​listeners.started(context)​​
  • ​​总结​​

什么是SpringBoot

日常开发中采用的是开源的若依框架,也就是SpringBoot框架,那么什么是SpringBoot框架呢?
SpringBoot是一款开箱即用框架,提供各种默认配置来简化项目配置,让我们的Spring应用变的更轻量化、更快的入门,在主程序执行main函数就可以运行,也可以打包你的应用为jar并通过使用java -jar来运行你的Web应用。 使用SpringBoot只需很少的配置,大部分的时候直接使用默认的配置即可。同时后续也可以与Spring Cloud的微服务无缝结合。

SpringBoot启动流程

SpringBoot启动流程涉及到的步骤相对来说容易理解,这里我先准备一个启动类

SpringBoot启动流程大揭秘_springboot启动流程


类需要标注@SpringBootApplication的注解,然后就可以直接以main函数的方式执行SpringApplication.run(DemoApplication.class, args);就可以启动项目,非常简单,下面我们再逐步分析每一步执行流程,main函数代码

@SpringBootApplication
public class DemoApplication
{
public static void main(String[] args)
{
SpringApplication.run(DemoApplication.class, args);
}
}

SpringApplication.run

首先我们跟进SpringApplication.run(DemoApplication.class, args);方法可以看到

SpringBoot启动流程大揭秘_java_02


run方法是一个static方法,继续点击run(new Class[] { primarySource }, args);方法可以看到调用了另一个run方法

SpringBoot启动流程大揭秘_spring_03

SpringApplication初始化

在第二个static静态run方法中可以看到new了一个SpringApplication对象,同时继续调用其的run方法来启动SpringBoot项目,下面我们先来看一下SpringApplication对象是如何构建的,进入new SpringApplication(primarySources)的构造方法可以看到

SpringBoot启动流程大揭秘_springboot源码_04


其中primarySources就是启动类的class对象,继续跟进可以看到SpringApplication构造类加载信息

SpringBoot启动流程大揭秘_springboot源码_05

WebApplicationType

在SpringApplication的构造类中通过WebApplicationType.deduceFromClasspath();判断当前应用程序的容器,一共有三种容器,更进去可以看到WebApplicationType如图

SpringBoot启动流程大揭秘_java_06


可以看到包含三种容器REACTIVE、NONE、SERVLET,默认用的是WebApplicationType.SERVLET容器。

加载spring.factories

再回到SpringApplication对象继续往下看,可以看到this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();方法getBootstrapRegistryInitializersFromSpringFactories();从spring.factories中获取BootstrapRegistryInitializer,源码

SpringBoot启动流程大揭秘_springboot源码_07


加入源码解析

@SuppressWarnings("deprecation")
private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {
ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();
//从spring.factories中获取Bootstrapper集合,遍历转化为BootstrapRegistryInitializer并存入initializers
getSpringFactoriesInstances(Bootstrapper.class).stream()
.map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))
.forEach(initializers::add);
//从spring.factories中获取BootstrapRegistryInitializer集合并存入initializers,最后返回initializers集合
initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
return initializers;
}

继续回到SpringApplication对象继续往下看会看到

SpringBoot启动流程大揭秘_springboot启动流程_08


其中getSpringFactoriesInstances(ApplicationContextInitializer.class)和getSpringFactoriesInstances(ApplicationListener.class)分别表示从spring.factories中获取容器上下文相关初始化ApplicationContextInitializer和容器监听器相关初始化ApplicationListener

SpringBoot启动流程大揭秘_java_09


获取完容器上下文初始化和监听器初始化器之后通过setInitializers((Collection)和setListeners((Collection)分别放入List initializers和List listeners,这里我们来启动一下程序看一下是否是这样

SpringBoot启动流程大揭秘_springboot源码_10


SpringBoot启动流程大揭秘_java_11


可以看到已经将spring.factories中的配置加载进来了。

deduceMainApplicationClass

后面继续跟进可以看到deduceMainApplicationClass()理解为推断推论主程序类,debug可以看到获取了main函数所在的主程序类

SpringBoot启动流程大揭秘_spring boot_12

自定义spring.factories

增加一个类ApplicationInit实现上下文初始化接口ApplicationContextInitializer,重写initialize方法,

public class ApplicationInit implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("加载自定义ApplicationContextInitializer...");
}
}

同时增加项目spring.factories配置

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
com.ruoyi.web.controller.common.ApplicationInit

SpringBoot启动流程大揭秘_java_13


启动应用程序可以看到

SpringBoot启动流程大揭秘_springboot启动流程_14


初始化完成SpringApplication之后就可以运行run方法了

SpringBoot启动run

初始化完成之后就可以正式进入run阶段了

SpringBoot启动流程大揭秘_spring boot_15


结合run阶段的源码来看看启动流程

public ConfigurableApplicationContext run(String... args) {
//实例化一个计时器,统计项目启动时间
StopWatch stopWatch = new StopWatch();
//启动计时器
stopWatch.start();
//初始化上下文对象
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
//定义可配置上下文
ConfigurableApplicationContext context = null;
//设置系统headless属性默认值是true
configureHeadlessProperty();
//从spring.factories中获取运行监听器 getRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
//启动监听器
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//在命令行下启动应用带的参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//准备环境 prepareEnvironment
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 配置忽略的bean
configureIgnoreBeanInfo(environment);
//打印在src/main/resources下放入名字是banner的自定义文件
Banner printedBanner = printBanner(environment);
//根据webApplicationType调用工厂类创建应用程序上下文容器
context = createApplicationContext();
//设置一个启动器,设置应用程序启动
context.setApplicationStartup(this.applicationStartup);
//配置容器的基本信息
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新容器
refreshContext(context);
//在刷新上下文后调用
afterRefresh(context, applicationArguments);
//计时器停止
stopWatch.stop();
if (this.logStartupInfo) {
//打印启动完成的日志
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//监听应用上下文启动完成所有的运行监听器调用 started() 方法
listeners.started(context);
//遍历所有的 runner,调用 run 方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//异常处理,如果run过程发生异常
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}

try {
//所有的运行监听器调用running()方法,监听应用上下文
listeners.running(context);
}
catch (Throwable ex) {
//异常处理
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
//返回最终构建的容器对象ConfigurableApplicationContext
return context;
}

run方法开始先初始化一个计时器,开启计时,之后会初始化一个上下文对象

createBootstrapContext

初始化上下文对象,这里将初始化SpringApplication是从spring.factories中获取的bootstrapRegistryInitializers进行初始化

SpringBoot启动流程大揭秘_java_16


之后继续向下看到configureHeadlessProperty();该方法主要设置系统headless属性默认值是true,查看源码

SpringBoot启动流程大揭秘_springboot源码_17

getRunListeners

从spring.factories中获取运行监听器EventPublishingRunListener

SpringBoot启动流程大揭秘_springboot源码_18


debug该方法可以看到从配置中加载的运行监听器方法

SpringBoot启动流程大揭秘_springboot源码_19


后续继续启动监听器listeners.starting(),调用starting()方法

SpringBoot启动流程大揭秘_springboot启动流程_20

prepareEnvironment

我们继续看prepareEnvironment方法,跟进去可以看到当前方法涉及getOrCreateEnvironment、configureEnvironment、ConfigurationPropertySources、DefaultPropertiesPropertySource、bindToSpringApplication、convertEnvironment,我们来看一下源码

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 创建和配置环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置 property sources 和 profiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
//将environment.getPropertySources()放在第一个位置
ConfigurationPropertySources.attach(environment);
//运行监听器通知所有监听器环境准备完成
listeners.environmentPrepared(bootstrapContext, environment);
//Move the 'defaultProperties' property source so that it's the last source
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
//Bind the environment to the {@link SpringApplication}
bindToSpringApplication(environment);
//环境转换成StandardEnvironment
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
//返回环境配置对象ConfigurableEnvironment
return environment;
}

这里我们来debug看一下执行过程中环境加载情况

SpringBoot启动流程大揭秘_spring boot_21


加载完成之后执行configureIgnoreBeanInfo方法配置忽略的bean信息,继续往下看

printBanner

打印配置的banner文本信息

SpringBoot启动流程大揭秘_springboot启动流程_22


banner文件路径在\src\main\resources\banner.txt,可以通过更该文件内容展示不同的启动成功信息。

继续向下看,看到createApplicationContext方法创建应用程序的上下文容器,创建完之后,继续看prepareContext,该方法主要配置容器的基本信息

prepareContext

prepareContext也是一个重要的方法,我们来看一下源码方法

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置容器的环境变量
context.setEnvironment(environment);
//设置容器的ResourceLoader、ClassLoader、ConversionService
postProcessApplicationContext(context);
//获取所有初始化器调用initialize()初始化
applyInitializers(context);
//触发所有 SpringApplicationRunListener监听器的contextPrepared事件方法
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
//打印启动日志
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans 添加启动特定的单例bean
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());
}
// Load the sources 加载所有的资源
Set<Object> sources = getAllSources();
//断言资源必须非空
Assert.notEmpty(sources, "Sources must not be empty");
//Load beans into the application context 加载启动类 the context to load beans into 将启动类注入容器
load(context, sources.toArray(new Object[0]));
//触发所有SpringApplicationRunListener监听器contextLoaded方法
listeners.contextLoaded(context);
}

refreshContext

配置完容器基本信息后,刷新容器上下文refreshContext方法,

SpringBoot启动流程大揭秘_springboot源码_23


继续跟进去可以看到

SpringBoot启动流程大揭秘_springboot源码_24


这里我们看web容器的类,

SpringBoot启动流程大揭秘_java_25


源码如下,注释比较容易理解,这里不再详细介绍里面的每一步加载了,先写主流程

@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
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();
contextRefresh.end();
}
}
}

刷新容器上下文refreshContext方法之后看到afterRefresh是一个空方法,主要用于开发者拓展使用

SpringBoot启动流程大揭秘_springboot启动流程_26

listeners.started(context)

容器配置都完成之后,这时监听应用上下文启动完成所有的运行监听器调用 started() 方法,发布监听应用的启动事件,如图

SpringBoot启动流程大揭秘_springboot源码_27


后续继续执行callRunners方法遍历所有runner,调用run方法

SpringBoot启动流程大揭秘_springboot启动流程_28


上述都完成之后到了最后一步,执行listener.running方法

SpringBoot启动流程大揭秘_spring_29


运行所有运行监听器,该方法执行以后SpringApplication.run(DemoApplication.class, args)也就算执行完成了,那么SpringBoot的ApplicationContext也就启动完成了。

总结

SpringBoot的执行流程整体上分为两个部分,也就是SpringApplication的初始化和SpringApplication.run方法,所有的启动加载过程都在这两个方法中,一篇文章写的太多不方便阅读,另外个人觉得太长的文章也没有人有耐心看完,所以中间一些细节没有细究,后面会继续补充里面细节的内容,感谢大家的阅读,欢迎有问题的评论区留言,共同学习共同成长。


标签:run,SpringBoot,启动,流程,监听器,SpringApplication,environment,context,揭秘
From: https://blog.51cto.com/u_10917175/5925132

相关文章

  • 基于springboot架构 钉钉扫码登录第三方应用
    基于springboot架构钉钉扫码登录第三方应用​​获取appId及appSecret​​​​项目应用​​​​1.配置文件增加如下配置​​​​2.下载sdk​​​​3.将sdk引入项目​​​​3......
  • springboot项目打包没有生成target文件及jar包
    在pom文件中声明了pompom:打出来可以作为其他项目的maven依赖,在工程A中添加工程B的pom,A就可以使用B中的类。用在父级工程或聚合工程中。用来做jar包的版本控制。既不会出......
  • springboot项目搭建,访问controller接口失败
    错误表现:Thisapplicationhasnoexplicitmappingfor/error,soyouareseeingthisasafallback.在启动类上的注解:@EnableAutoConfiguration解决:使用这个注解......
  • SpringBoot+RabbitMQ实现消息订阅发送
    RabbitMQ安装(docker)拉取rabbitmq镜像文件#未指定版本,拉取最新版本root#dockerpullrabbitmq启动镜像root#dockerrun-d--hostnamemy-rabbit--na......
  • springMVC的简介和工作机制(工作流程)
    springMVC简介大部分java应用都是web应用,展现层是web应用最为重要的部分。Spring为展现层提供了一个优秀的web框架SpnngMVC。和众多其他web框架一样,它基于MVC的设计理念,此......
  • springboot集成kettle实现对接oracle数据
    简单记录一下springboot引用kettle对接数据第一步(这一步讲述了下载kettle、创建数据库连接、转换等,如果这一步会的可以略过,直接看第二步)先从kettle官网下载kettle,官网地......
  • 创建springboot过滤器
    springboot过滤器importorg.springframework.core.ParameterizedTypeReference;importorg.springframework.web.context.WebApplicationContext;importorg.springframewo......
  • springboot+mybatis+log4j日志sql输出和文件输出
    pom引入依赖:<dependency><!--排除spring-boot-starter-logging--><groupId>org.springframework.boot</groupId><artifactId>sprin......
  • Hello,SpringBoot
    什么是SpringBoot一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置,youcan"justrun",能迅速的开发web应用,几行代码开发......
  • SpringBoot内置tomcat启动过程及原理
    1背景SpringBoot是一个框架,一种全新的编程规范,他的产生简化了框架的使用,同时也提供了很多便捷的功能,比如内置tomcat就是其中一项,他让我们省去了搭建tomcat容器,生成war,部署,......