概览SpringBoot的run方法的主干逻辑


思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。

作者:毅航😜


对于SpringBootrun方法的分析网上已经有很多教程了,本文想试着从全局出发,重新梳理SpringBootrun方法的整体脉络,使你不至于过分专注细节而迷失在run方法繁琐的调用逻辑中。

前言

SpringBoot是基于Spring 开发的一种轻量级的全新框架,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring 应用的整个搭建和开发过程。

开发者可以通过SpringBoot轻松地创建独立的,基于生产级别的基于Spring的应用程序。SpringBoot 的出现极大的降低了开发应用的难度。除此之外,SpringBoot还具有如下特点:

  1. 可独立运行的Spring项目:Spring Boot可以以jar包的形式独立运行。

  2. 内嵌的Web容器:Spring Boot可以选择内嵌TomcatJetty或者Undertow,无须以war包形式部署项目。

  3. 简化的Maven配置:Spring提供推荐的基础 POM 文件来简化Maven 配置。

  4. 自动配置: SpringBoot会根据项目依赖来自动配置Spring、SpringMVC等框架,极大地减少项目要使用的配置。

构建SpringBoot项目

如果你曾经有过Spring、SpringMVC的开发经验,你一定会对其中繁琐的xml配置感到深恶痛绝,而SpringBoot的出现恰好可以解决这一痛点问题,SpringBoot可以让你更加快捷的搭建一个企业级应用。具体代码如下:

java 复制代码
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * SpringBoot应用启动类
 * */
@SpringBootApplication
public class LearnSpringBootApplication {

    public static void main(String[] args) {
        // 启动SpringBoot
        SpringApplication.run(LearnSpringBootApplication.class, args);
    }
}

通过上述代码不难发现,启动一个SpringBoot应用非常简单,我们只需如下几步:

  1. 提供一个SpringBoot的启动类LearnSpringBootApplication(注:启动类的名称可任意指定)
  2. 启动类上标注一个@SpringBootApplication注解
  3. 编写一个main方法,调用SpringApplication中的run方法

经过上述三步操作,就可以构建出一个简单的SpringBoot应用,随后运行main方法即可启动一个SpringBoot的应用。

可以注意到,在main方法中仅需调用一个run方法就能完成SpringBoot应用的启动。那这个run方法背后究竟又做了那些操作呢?接下来,我们便聚焦在SpringApplicationrun方法中,看看run方法背后完成了那些操作。

run方法背后的逻辑

进入到SpringApplicationrun方法后,其最终入调用到如下的方法信息,相关代码如下所示:

SpringApplication # run

java 复制代码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

可以看到SpringApplication中对方法进行了重载,其最终会调用到run(Class<?>[] primarySources, String[] args)

此处run方法逻辑包含了两个操作:

  1. 构建一个SpringApplication对象
  2. 执行SpringApplication对象的run方法信息

接下来,我们将围绕上述的两个操作展开分析SpringBootrun方法背后的逻辑。

创建SpringApplication对象

java 复制代码
public SpringApplication(Class<?>... primarySources) {
   this(null, primarySources);
}
java 复制代码
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();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

可以看到对于SpringApplication而言,其在构建时包含如下信息的构建:

  1. ResourceLoader 指明资源加载器,这个暂时不用太过关注,应为其默认为null
  2. webApplicationType 推断当前web应用类型,可通过一个deduceFromClasspath方法推断出的。
  3. 随后设置了setInitializers、setListeners两个列表,分别是一堆InitializerListener,其都是通过getSpringFactoriesInstances方法获取。
  4. 此外,还通过primarySources、mainApplicationClass记录了启动主要资源类,也就是之前传入的LearnSpringBootApplication.class

面对上述代码中诸多的陌生对象,你肯定什么都不清楚,不知道每个变量有什么用,是干嘛的,这没有关系。因为第一次分析你只要熟悉它的脉络就可以。知道在SpringApplication的构造方法中,会设置两个集合变量InitializerListener,了解这些就够了。

等之后你有时间,再逐个去了解每个变量或者组件的作用就可以了。事实上, SpringApplication的创建时的细节分析 你可以慢慢拆解上面的每一步,单独看看每一个组件大体是作什么的,这就属于细节研究了。

比如,你可以研究下ResourceLoader是什么? 通过阅读它的类注释后可以发现,ResourceLoader 类负责使用ClassLoader加载ClassPath下的class和各种配置文件的。

对于webApplicationType类型如何被推断的?其本质就是根据几个静态变量定义的类全限定名称来进行判断的。具体而言,其会判断classPath下是否存在DispatcherServlet、DispatcherHandler等类来推断出类型。如果使用了web-starter则默认推断出为Servlet类型的应用。

至于primarySources、mainApplicationClass这个两个变量记录了启动类信息LearnSpringBootApplication.class, 其目的在于为了后续扫描包路径信息,完成自动配置等考虑的。

至于最后两个集合变量InitializerListener如何设置的,则比较考验阅读源码的能力了。其基本原理是通过ClassLoader扫描了classPath下所有META-INF/spring.factories这个目录中的文件,通过指定的factoryType,也就是接口名称,获取对应的所有实现类,并且实例化成对象,返回成一个list列表。 比如, factoryType=ApplicationContextInitializer 就返回这个接口在META-INF/spring.factories定义的所有的实现类,并实例化为一个列表List ApplicationContextInitializerApplicationListener同理。这里面其实有很多细节,大量使用了类加载器、缓存机制,反射机制等,有兴趣的话可以仔细研究下。

总结来看,上述构建SpringApplication的过程虽然会包括很多东西,但别慌,其概括成一句话就是:设定某些属性信息,然后通过classLoader获取classPath下指定位置某些接口的实现类和实例对象列表。

进一步, 构建SpringApplication时的相关逻辑如图所示:

熟悉了SpringApplication 的创建,接着我们该分析它的run(String ... args) 方法了。

SpringApplication Run的脉络

run(String... args) 的逻辑如下:

java 复制代码
public ConfigurableApplicationContext run(String... args) {
        // .......省略其他无关代码
        listeners.starting();
        try {
            // 构建一个应用参数解析器
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            
		    // 加载系统的属性配置信息
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            // 用于控制是否忽略BeanInfo的配置
            configureIgnoreBeanInfo(environment);
            // 打印banner信息
            Banner printedBanner = printBanner(environment);
            // 创建一个容器,类型为ConfigurableApplicationContext
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            // 容器准备工作 (可暂时忽略)
            prepareContext(context, environment, listeners, 
            // 解析传入参数信息
            applicationArguments, printedBanner);
            // 容器刷新 (重点关注)
            refreshContext(context);
            afterRefresh(context, applicationArguments);
           
        }
         // .......省略其他无关代码
    
   
        return context;
    }

我们重点分析其中的refresh方法信息,对于refresh而言,其调用逻辑如下所示:

(注:如果你熟悉Spring中容器刷新的操作流程,相信你一定会秒懂图中蓝色方框内逻辑)

进一步,上面代码虽然看着复杂, 但本质主要就是执行了一堆方法。从方法名字看出,都是围绕Context、Environment这些术语。也就是围绕容器和配置文件组织的逻辑。 另外,SpringBoot整个run方法中有几个很关键扩展点,设计SpringApplicationRunListeners、Runners等扩展入口。此外容器创建、刷新等也要各自的扩展点,对容器的增强扩展,如beanFactoryPostProcessor,对Bean的增加扩展,如beanPostProcessor

(注:原图地址:baijiahao.baidu.com/s?id=171315...)

最后通过一张图来概括上面run方法脉络,其中:

  1. 黑色部分直观的反映了扩展逻辑相关逻辑
  2. 白色部分是run方法每个方法的字面理解,只是每一步有很多扩展点和做的事情比较多,让你感觉会有点云里雾里的。
  3. 蓝色部分,概括了核心逻辑。也就是SpringBoot启动,说白了我们核心就是要找到这三点:自动装配配置、Spring容器的创建、web容器启动。

总结

如果你第一次接触SpringBoot源码,可能会感到有些"不适"。这一定程度上是因为你过多的关注了源码细节,事实上,从细节中可以学习到知识,同时从主干脉络上也能学到知识。

而本文的主要作用是先为了梳理清楚一个run方法的脉络信息,让你熟悉run脉络的启动逻辑,让你清楚run的主干逻辑,之后如果你有兴趣要继续深入研究SpringBoot的启动逻辑只需沿着这一主干分析即可。

相关推荐
骄马之死19 小时前
SpringMVC + SpringBoot 核心知识点总结
java·spring boot·后端
GoGeekBaird20 小时前
Anthropic技能"(Skills)的经验分享
后端
王码码203520 小时前
多台服务器怎么统一看状态?Beszel 轻量监控,搭起来不费事
运维·服务器·后端·安全·阿里云·接口·web
郑洁文21 小时前
基于Spring Boot的流浪动物救助网站
java·spring boot·后端·毕设·流浪动物救助
螺丝钉code21 小时前
JAVA项目 Claude code CLAUDE.md 到底应该怎么写
java·人工智能·claude code
指令集梦境1 天前
Cursor + Spring Boot实战:从零写一个RESTful API
spring boot·后端·restful
摇滚侠1 天前
Maven 入门+高深 单一架构案例 54-59
java·架构·maven·intellij-idea
VidDown1 天前
Webhook 调试器:让第三方回调“原形毕露”
java·开发语言·javascript·编辑器·postman
码云之上1 天前
聊聊如何设计一个高效、稳定的 Node.js 接入层
前端·后端·node.js
折哥的程序人生 · 物流技术专研1 天前
Java 23 种设计模式:从踩坑到精通 | 原型模式 —— 克隆对象,深拷贝与浅拷贝的坑你踩过吗?
java·设计模式·架构·原型模式·单一职责原则