likes
comments
collection
share

概览SpringBoot的run方法主干逻辑

作者站长头像
站长
· 阅读数 15

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


对于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可以让你更加快捷的搭建一个企业级应用。具体代码如下:

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


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对象

public SpringApplication(Class<?>... primarySources) {
   this(null, primarySources);
}
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时的相关逻辑如图所示:

概览SpringBoot的run方法主干逻辑

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

SpringApplication Run的脉络

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

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而言,其调用逻辑如下所示:

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

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

概览SpringBoot的run方法主干逻辑

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

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

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

总结

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

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

转载自:https://juejin.cn/post/7292552003016474624
评论
请登录