SpringBoot启动全流程源码解析

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SpringBoot启动全流程源码解析

SpringBoot启动过程流程图

源码解析

首先,我们先来看下SpringBoot项目的启动类

@SpringBootApplication

  public class SpringDemoApplication {

    public static void main(String[] args) {

    SpringApplication.run(SpringDemoApplication.class, args);

    }

  }

一个是@SpringBootApplication,参考另一篇文章SpringBoot自动配置实现原理及源码解析(2.3.x)

另一个关键点是SpringApplication.run()方法,这是一个静态方法,我们详细看下代码:

/**

  • 静态方法
    */
    public static ConfigurableApplicationContext run(Class<?> primarySource, String… args) {
    return run(new Class<?>[]{primarySource}, args);
    }

/**

  • 调用此方法启动会使用默认设置和用户提供的参数args
    */
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // 实例化SpringApplication,然后调用run
    return new SpringApplication(primarySources).run(args);
    }
    可以看到代码new SpringApplication(),new了一个这个对象,然后调用run,我们先看看SpringApplication构造函数:

public SpringApplication(Class<?>… primarySources) {

this(null, primarySources);

}

public SpringApplication(ResourceLoader resourceLoader, Class<?>… primarySources) {

this.resourceLoader = resourceLoader;

Assert.notNull(primarySources, “PrimarySources must not be null”);

// 把SpringDemoApplication作为primarySources属性存储起来

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

// 从classpath中推断是否为web应用

this.webApplicationType = WebApplicationType.deduceFromClasspath();

// 获取启动加载器

this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));

// 设置初始化器(Initializer),最后会调用这些功能

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

// 设置监听器(Listener)

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

// 获取main方法所在的类

this.mainApplicationClass = deduceMainApplicationClass();

}

基本就是做如下几件事情:

配置primarySources

配置环境是否为web环境

创建初始化构造器setInitializers

创建应用监听器

配置应用主方法所在类(就是main方法所在类)

基本上就是做一些必要的属性初始化和赋值,接下来我们看下关键方法run

/**

  • 运行spring应用程序,创建并刷新一个新的 {@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();
    // 创建启动上下文对象
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    // 第一步:获取并启动监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    // 第二步:准备环境
    ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
    configureIgnoreBeanInfo(environment);
    // 第三步:打印banner,就是启动的时候在console的spring图案
    Banner printedBanner = printBanner(environment);
    // 第四步:创建spring容器
    context = createApplicationContext();
    context.setApplicationStartup(this.applicationStartup);
    // 第五步:spring容器前置处理
    prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
    // 第六步:刷新容器
    refreshContext(context);
    // 第七步:spring容器后置处理
    afterRefresh(context, applicationArguments);
    stopWatch.stop(); // 结束计时器并打印,这就是我们启动后console的显示的时间
    if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    }
    // 发出启动结束事件
    listeners.started(context);
    // 执行runner的run方法
    callRunners(context, applicationArguments);
    } catch (Throwable ex) {
    // 异常处理,如果run过程发生异常
    handleRunFailure(context, ex, listeners);
    throw new IllegalStateException(ex);
    }

try {

listeners.running(context);

} catch (Throwable ex) {

// 异常处理,如果run过程发生异常

handleRunFailure(context, ex, null);

throw new IllegalStateException(ex);

}

// 返回最终构建的容器对象

return context;

}

基本流程如下:

启动一个计时器,启动完成后会打印耗时

获取并启动监听器 SpringApplicationRunListeners

配置环境 ConfigurableEnvironment

Banner配置,就是控制台的那个spirng

应用上下文模块(前置处理、刷新、后置处理) ConfigurableApplicationContext

发出启动结束事件并结束计时

这里的每一个方法都是做了很多事情,接下来我们一步步深入看下

run方法第一步:获取并启动监听器

这里的启动监听就是我们需要监听SpringBoot的启动流程监听,实现SpringApplicationRunListener类即可监听

/**

  • 获取运行监听的监听者们,在对应的阶段会发送对应的事件到监听者
  • @param args
  • @return
    */
    private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
    return new SpringApplicationRunListeners(logger,
    getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
    this.applicationStartup);
    }
    1

SpringApplicationRunListener类如下:

public interface SpringApplicationRunListener {

/**

  • 当调用run方法后会立即调用,可以用于非常早期的初始化
    */
    default void starting(ConfigurableBootstrapContext bootstrapContext) {
    starting();
    }

/**

  • 环境准备好之后调用
    */
    default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
    ConfigurableEnvironment environment) {
    environmentPrepared(environment);
    }

/**

  • 在加载资源之前,ApplicationContex准备好之后调用
    */
    default void contextPrepared(ConfigurableApplicationContext context) {
    }

/**

  • 在加载应用程序上下文但在其刷新之前调用
    */
    default void contextLoaded(ConfigurableApplicationContext context) {
    }

/**

  • 上下文已经刷新且应用程序已启动且所有{@link CommandLineRunner commandLineRunner}
  • 和{@link ApplicationRunner ApplicationRunners}未调用之前调用
    */
    default void started(ConfigurableApplicationContext context) {
    }

/**

  • 当应用程序上下文被刷新并且所有{@link CommandLineRunner commandLineRunner}
  • 和{@link ApplicationRunner ApplicationRunners}都已被调用时,在run方法结束之前立即调用。
    */
    default void running(ConfigurableApplicationContext context) {
    }

/**

  • 在启动过程发生失败时调用
    */
    default void failed(ConfigurableApplicationContext context, Throwable exception) {
    }
    }

run方法第二步:准备环境

/**

  • 创建并配置SpringBooty应用j将要使用的Environment

  • @param listeners
  • @param bootstrapContext
  • @param applicationArguments
  • @return
    */
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // 根据不同的web类型创建不同实现的Environment对象
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置环境
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    // 发送环境已准备完成事件
    listeners.environmentPrepared(bootstrapContext, environment);
    DefaultPropertiesPropertySource.moveToEnd(environment);
    // 根据命令行参数中spring.profiles.active属性配置Environment对象中的activeProfile(比如dev、prod、test)
    configureAdditionalProfiles(environment);
    // 绑定环境中spring.main属性绑定到SpringApplication对象中
    bindToSpringApplication(environment);
    // 如果用户使用spring.main.web-application-type属性手动设置了webApplicationType
    if (!this.isCustomEnvironment) {
    // 将环境对象转换成用户设置的webApplicationType相关类型,他们是继承同一个父类,直接强转
    environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
    deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
    }

这里主要有如下过程:

创建配置环境 ConfigurableEnvironment

加载属性文件资源

配置监听

run方法第三步:打印banner

/**

  • 打印banner

  • @param environment
  • @return
    */
    private Banner printBanner(ConfigurableEnvironment environment) {
    // banner模式,可以是console、log、off
    if (this.bannerMode == Banner.Mode.OFF) {
    return null;
    }
    ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
    : new DefaultResourceLoader(null);
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
    if (this.bannerMode == Mode.LOG) {
    return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }

基本就是依据不同情况打印banner而已,比较简单

run方法第四步:创建spring容器

最终获取到ConfigurableApplicationContext上下文对象

run方法第五步:spring容器前置处理

/**

  • Spring容器准备
    */
    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
    ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
    ApplicationArguments applicationArguments, Banner printedBanner) {
    // 设置上下文环境
    context.setEnvironment(environment);
    //
    postProcessApplicationContext(context);
    // 执行所有ApplicationContextInitializer对象的initialize方法(这些对象是通过读取spring.factories加载)
    applyInitializers(context);
    // 发布上下文准备完成事件到所有监听器
    listeners.contextPrepared(context);
    bootstrapContext.close(context);
    if (this.logStartupInfo) {
    logStartupInfo(context.getParent() == null);
    logStartupProfileInfo(context);
    }
    //
    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 sources = getAllSources();
    Assert.notEmpty(sources, “Sources must not be empty”);
    // 加载bean到上下文
    load(context, sources.toArray(new Object[0]));
    // 发送上下文加载完成事件
    listeners.contextLoaded(context);
    }

run方法第六步:刷新容器【关键】

/**

  • 刷新应用程序上下文

  • @param context
    */
    private void refreshContext(ConfigurableApplicationContext context) {
    // 注册一个关闭钩子,在jvm停止时会触发,然后退出时执行一定的退出逻辑
    if (this.registerShutdownHook) {
    try {
    // 添加:Runtime.getRuntime().addShutdownHook()
    // 移除:Runtime.getRuntime().removeShutdownHook(this.shutdownHook)
    context.registerShutdownHook();
    } catch (AccessControlException ex) {
    // Not allowed in some environments.
    }
    }
    // ApplicationContext真正开始初始化容器和创建bean的阶段
    refresh((ApplicationContext) context);
    }

protected void refresh(ApplicationContext applicationContext) {

Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);

refresh((ConfigurableApplicationContext) applicationContext);

}

protected void refresh(ConfigurableApplicationContext applicationContext) {

applicationContext.refresh();

}

调用应用上下文对象的refresh()方法,接下来我i门到ConfigurableApplicationContext类中去看下这个方法

public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {

void refresh() throws BeansException, IllegalStateException;

}

这是一个接口,且这个类是在spring框架中,非springboot,它的实现类共有三个

AbstractApplicationContext是一个抽象类,其余两个类都继承了它,我们来看看这个抽象类的代码:

@Override

public void refresh() throws BeansException, IllegalStateException {

synchronized (this.startupShutdownMonitor) {

StartupStep contextRefresh = this.applicationStartup.start(“spring.context.refresh”);

// 第一步:准备更新上下时的预备工作

prepareRefresh();

// 第二步:获取上下文内部BeanFactory

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 第三步:对BeanFactory做预备工作

prepareBeanFactory(beanFactory);

try {

// 第四步:允许在上下文子类中对bean工厂进行post-processing

postProcessBeanFactory(beanFactory);

StartupStep beanPostProcess = this.applicationStartup.start(“spring.context.beans.post-process”);

// 第五步:调用上下文中注册为bean的工厂 BeanFactoryPostProcessor

invokeBeanFactoryPostProcessors(beanFactory);

// 第六步:注册拦截bean创建的拦截器

registerBeanPostProcessors(beanFactory);

beanPostProcess.end();

// 第七步:初始化MessageSource(国际化相关)

initMessageSource();

// 第八步:初始化容器事件广播器(用来发布事件)

initApplicationEventMulticaster();

// 第九步:初始化一些特殊的bean

onRefresh();

// 第十步:将所有监听器注册到前两步创建的事件广播器中

registerListeners();

// 第十一步:结束bean的初始化工作(主要将所有单例BeanDefinition实例化)

finishBeanFactoryInitialization(beanFactory);

// 第十二步:afterRefresh(上下文刷新完毕,发布相应事件)

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();

}

}

}

这里有非常多的步骤,上下文对象主要的bean也是在这里进行处理的,具体的说明可以看注释

@Override

public final void refresh() throws BeansException, IllegalStateException {

try {

super.refresh();

}

catch (RuntimeException ex) {

WebServer webServer = this.webServer;

if (webServer != null) {

webServer.stop();

}

throw ex;

}

}

主要还是调用父类方法,没有什么特殊的

run方法/第七步:spring容器后置处理 afterRefresh()

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {

}

这是一个空的方法

run方法启动后

主要做如下几件事情:

发出启动结束事件

执行实现ApplicationRunner、CommandLineRunner的run方法

发布应用程序已启动(ApplicationStartedEvent)事件

run方法异常处理

如果run方法的处理过程中发生异常,则对exitCode进行相应处理

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,

SpringApplicationRunListeners listeners) {

try {

try {

handleExitCode(context, exception);

if (listeners != null) {

listeners.failed(context, exception);

}

} finally {

reportFailure(getExceptionReporters(context), exception);

if (context != null) {

context.close();

}

}

} catch (Exception ex) {

logger.warn(“Unable to close ApplicationContext”, ex);

}

ReflectionUtils.rethrowRuntimeException(exception);

}

至此,所有SpringBoot的启动流程已经完成,你的项目也顺利的跑起来了

原文链接:https://www.cnblogs.com/lanblogs/p/14888244.html

相关文章
|
18天前
|
前端开发 Java
表白墙/留言墙 —— 初级SpringBoot项目,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
文章通过一个表白墙/留言墙的初级SpringBoot项目实例,详细讲解了如何进行前后端开发,包括定义前后端交互接口、创建SpringBoot项目、编写前端页面、后端代码逻辑及实体类封装的全过程。
45 3
表白墙/留言墙 —— 初级SpringBoot项目,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
18天前
|
前端开发 Java 数据安全/隐私保护
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
文章通过一个简单的SpringBoot项目,详细介绍了前后端如何实现用户登录功能,包括前端登录页面的创建、后端登录逻辑的处理、使用session验证用户身份以及获取已登录用户信息的方法。
71 2
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
|
18天前
|
缓存 Java Spring
servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header
文章比较了在Servlet和Spring Boot中获取Cookie、Session和Header的方法,并提供了相应的代码实例,展示了两种方式在实际应用中的异同。
78 3
servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header
|
4天前
|
JavaScript Java 关系型数据库
自主版权的Java诊所管理系统源码,采用Vue 2、Spring Boot等技术栈,支持二次开发
这是一个自主版权的Java诊所管理系统源码,支持二次开发。采用Vue 2、Spring Boot等技术栈,涵盖患者管理、医生管理、门诊管理、药店管理、药品管理、收费管理、医保管理、报表统计及病历电子化等功能模块。
|
1月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
307 37
|
1月前
|
JavaScript Java 关系型数据库
毕设项目&课程设计&毕设项目:基于springboot+vue实现的在线考试系统(含教程&源码&数据库数据)
本文介绍了一个基于Spring Boot和Vue.js实现的在线考试系统。随着在线教育的发展,在线考试系统的重要性日益凸显。该系统不仅能提高教学效率,减轻教师负担,还为学生提供了灵活便捷的考试方式。技术栈包括Spring Boot、Vue.js、Element-UI等,支持多种角色登录,具备考试管理、题库管理、成绩查询等功能。系统采用前后端分离架构,具备高性能和扩展性,未来可进一步优化并引入AI技术提升智能化水平。
毕设项目&课程设计&毕设项目:基于springboot+vue实现的在线考试系统(含教程&源码&数据库数据)
|
1月前
|
Java 关系型数据库 MySQL
毕设项目&课程设计&毕设项目:springboot+jsp实现的房屋租租赁系统(含教程&源码&数据库数据)
本文介绍了一款基于Spring Boot和JSP技术的房屋租赁系统,旨在通过自动化和信息化手段提升房屋管理效率,优化租户体验。系统采用JDK 1.8、Maven 3.6、MySQL 8.0、JSP、Layui和Spring Boot 2.0等技术栈,实现了高效的房源管理和便捷的租户服务。通过该系统,房东可以轻松管理房源,租户可以快速找到合适的住所,双方都能享受数字化带来的便利。未来,系统将持续优化升级,提供更多完善的服务。
毕设项目&课程设计&毕设项目:springboot+jsp实现的房屋租租赁系统(含教程&源码&数据库数据)
|
18天前
|
前端开发 Java 数据库连接
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
本文是一份全面的表白墙/留言墙项目教程,使用SpringBoot + MyBatis技术栈和MySQL数据库开发,涵盖了项目前后端开发、数据库配置、代码实现和运行的详细步骤。
24 0
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
18天前
|
存储 JSON 算法
JWT令牌基础教程 全方位带你剖析JWT令牌,在Springboot中使用JWT技术体系,完成拦截器的实现 Interceptor (后附源码)
文章介绍了JWT令牌的基础教程,包括其应用场景、组成部分、生成和校验方法,并在Springboot中使用JWT技术体系完成拦截器的实现。
32 0
JWT令牌基础教程 全方位带你剖析JWT令牌,在Springboot中使用JWT技术体系,完成拦截器的实现 Interceptor (后附源码)
|
18天前
|
Java 数据库连接 mybatis
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
该文档详细介绍了如何在Springboot Web项目中整合Mybatis,包括添加依赖、使用`@MapperScan`注解配置包扫描路径等步骤。若未使用`@MapperScan`,系统会自动扫描加了`@Mapper`注解的接口;若使用了`@MapperScan`,则按指定路径扫描。文档还深入分析了相关源码,解释了不同情况下的扫描逻辑与优先级,帮助理解Mybatis在Springboot项目中的自动配置机制。
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码