首页 > 自考资讯 > 高考百科

一文了解Spring Boot启动类SpringApplication(Spring Boot的启动过程)

小条 2024-07-05

本文由华为云社区分享《【Spring Boot 源码学习】初识 SpringApplication-云社区-华为云》,作者:花子。

引言

在上一篇博文中,花子重点介绍了Spring Boot的核心特性,让大家全面了解Spring Boot的自动配置原理以及自动配置核心组件的操作流程。只有了解这些基本组件和功能,您才能更清楚地了解集成其他第三方库的启动器时使用的自动配置功能。

当您学习上述核心Spring Boot 功能时,您可能希望启动自己的新Spring Boot 项目并对其进行调试以查看具体的执行过程。本文我们从Spring Boot的启动类SpringApplication入手,了解Spring Boot启动流程相关的源码和知识点。

主要内容

1. Spring Boot 应用程序的启动

在这篇博文《【Spring Boot 源码学习】@SpringBootApplication 注解-云社区-华为云》 中,我们创建了一个基于Spring Boot 的新测试项目。

4802c48511124edea22844f96dfa4098~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720755792&x-signature=yWk%2FZ1bfyeTDXWGS0PNkZXiVyXQ%3D 上图中的DemoApplication就是我们Spring Boot项目的入口类。

同时可以看到DemoApplication的main方法直接调用了SpringApplication的静态方法run。这是用来启动整个Spring Boot项目的。

首先我们看一下run方法的源码。

公共静态ConfigurableApplicationContext run(Class?primarySource, String. args) { return run(new Class?[] {primarySource }, args);} 公共静态ConfigurableApplicationContext run(Class?[] PrimarySources, String[] args) { return new SpringApplication(primarySources).run(args);} 如果你读了上面的run方法,你会发现SpringApplication对象其实是new的【它的构造参数primarySources就是将要加载的主要资源类,一般是SpringBoot入口类】 。它的run方法【参数args是传递给应用程序的参数信息】启动并返回应用程序上下文对象ConfigurableApplicationContext。

通过观察内部run方法实现,你也可以将其写在自己的Spring Boot启动入口类中,如下所示:

@SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { SpringApplication springApplication=new SpringApplication(DemoApplication.class) //现在可以通过调用SpringApplication 提供的setXX 或addXX 方法来自定义配置springApplication.run (args); } ); }}

2. SpringApplication 的实例化

我们解释说我们正在实例化SpringApplication。我们看一下源码【Spring Boot 2.7.9】。

公共SpringApplication(Class?primarySources) { this(null, PrimarySources); @SuppressWarnings({ 'unchecked', 'rawtypes' }) 公共SpringApplication(ResourceLoader resourceLoader, Class?primarySources) { this.resourceLoader=resourceLoader; Assert.notNull(primarySources, 'PrimarySources 不能为null'); this.primarySources=new LinkedHashSet(Arrays.asList(primarySources)); //推断Web 应用程序类型this.webApplicationType=WebApplicationType.deduceFromClasspath();加载并初始化BootstrapRegistryInitializer 及其实现类this.bootstrapRegistryInitializers=new ArrayList( getSpringFactoriesInstances(BootstrapRegistryInitializer.class)) //加载并初始化ApplicationContextInitializer 及其实现类setInitializers( (Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //加载并初始化初始化ApplicationListener及其实现类setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //推断入口类this.mainApplicationClass=deduceMainApplicationClass() } 上面可以看到,SpringApplication提供了两种构造方法。它的所有核心逻辑都在第二个构造函数中实现。

2.1 构造方法参数

从上面的源码中我们可以看到SpringApplication的第二个构造函数有两个参数:

ResourceLoader资源加载器:ResourceLoader用于默认使用DefaultResourceLoader时输出对应的banner信息。在实际操作中,如果banner内容没有按照Spring Boot的“约定”放在classpath下或者文件名不是banner.*格式,默认资源加载器就无法加载相应的banner信息。此时,您可以使用ResourceLoader来指定需要加载的文件的路径(我们稍后会这样做,请耐心等待)。

Class?primarySources:主要bean 来源。默认情况下,我们传递Spring Boot入口类(即main方法所在的类)。最重要的是。当用作项目的引导类时,该类必须满足一个条件:使用@EnableAutoConfiguration 注释或注释组合。从之前的《【Spring Boot 源码学习】@SpringBootApplication 注解-云社区-华为云》博文中,我们已经知道@SpringBootApplication注解中包含了@EnableAutoConfiguration注解,因此使用@SpringBootApplication注解的类也可以作为参数传递。当然,primarySources 可以传递给其他常规类,但只有使用@EnableAutoConfiguration 注解的类才能传递以启用Spring Boot 自动配置。

有些人可能对变量参数PrimarySources的描述有点困惑。举例说明如何使用其他引导类作为入口类来启动Spring Boot 项目。

首先在入口类DemoApplication同目录下创建SecondApplication类,并使用@SpringBootApplication注解。

@SpringBootApplicationpublic class SecondApplication {}现在将DemoApplication 更改为:

public class DemoApplication { public static void main(String[] args) { SpringApplication.run(SecondApplication.class, args); 最后,运行DemoApplication 的main 方法。

a7b81125525b42229079f928a004c59c~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720755792&x-signature=eWjcZY%2FYAaa%2BCo6dmpyfCEbWXPk%3D 从上图可以看出,应用程序仍然可以成功启动并完成自动配置。因此,决定启动Spring Boot的入口类不一定是main方法所在的类,而是直接或间接使用@EnableAutoConfiguration注解的类。

如果你查看SpringApplication源码,还可以看到它提供了添加primarySource的方法,如下所示。

public void addPrimarySources(CollectionClass?AdditionalPrimarySources) { this.primarySources.addAll(AdditionalPrimarySources);} 如果使用1中的最后一个方法启动Spring Boot,则可以通过调用addPrimarySources方法添加额外的primarySources。

让我们回到SpringApplication 的构造函数。您将看到以下代码:

this.primarySources=new LinkedHashSet(Arrays.asList(primarySources)); 上面将primarySources参数转换为LinkedHashSet集合并赋值给SpringApplication的私有成员变量SetClass?

知识点:LinkedHashSet是Java Collection Framework中的一个类,继承自HashSet,因此具有哈希表的查找性能。这是一种同时使用链表和哈希表功能的数据结构,其中链表用于维护元素的插入顺序。这意味着当你向LinkedHashSet添加元素时,元素会按照添加的顺序存储,并且可以被遍历和输出。

此外,LinkedHashSet还保证其元素的唯一性。也就是说,集合中只有一个重复元素的副本。

如果需要频繁遍历一个集合,LinkedHashSet 比HashSet 更高效,因为它通过维护一个记录元素添加顺序的双向链表来支持按插入顺序迭代。但是,请注意LinkedHashSet 不是线程安全的。当多个线程同时访问集合容器时,可能会出现并发问题。

2.2 Web 应用类型推断

继续检查源代码。这里,我们通过调用WebApplicationType的deduceFromClasspath方法来推断Web应用程序的类型。

this.webApplicationType=WebApplicationType.deduceFromClasspath(); 我们继续看WebApplicationType的源代码。

public enum WebApplicationType { //非Web 应用程序类型NONE, //基于Servlet 的Web 应用程序类型SERVLET, //基于响应式的Web 应用程序类型REACTIVE; private static Final String[] SERVLET_INDICATOR_CLASSES={ 'javax.servlet.Servlet', ' org.springframework.web.context.ConfigurableWebApplicationContext' }; 私有静态最终字符串WEBMVC_INDICATOR_CLASS='org.springframework.web.servlet.DispatcherServlet'; 私有静态最终字符串WEBFLUX_INDICATOR_CLASS='org.springframework.web.DispatcherHandler'; Final String JERSEY_INDICATOR_CLASS='org.glassfish.jersey.servlet.ServletContainer'; static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) !ClassUtils.isPresent( JERSEY_ INDICATOR_CLASS, null ) ) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } return WebApplicationType.SERVLET; }应用程序类型的枚举类。该枚举类包含三段逻辑。

枚举:非Web 应用程序、基于servlet 的Web 应用程序和基于响应式的Web 应用程序。

下面用于推理的常量

推断类型的方法deducFromClasspath:

如果DispatcherHandler 存在并且DispatcherServlet 和ServletContainer 都不存在,则返回类型将为WebApplicationType.REACTIVE。

如果不存在Servlet或ConfigurableWebApplicationContext,则表示当前应用程序是非Web应用程序,并返回WebApplicationType.NONE。

如果应用程序不是反应式Web 应用程序,并且servlet 和ConfigurableWebApplicationContext 都存在,则返回WebApplicationType.SERVLET。

可以看到上面的deduceFromClasspath方法中,使用了ClassUtils的isPresent方法来进行判断过程。该工具类方法通过反射创建指定的类,并根据创建过程中是否抛出异常来判断该类是否存在。

dbd8190cb79c4507adf7c9217d03fd8c~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720755792&x-signature=Wn21n2nU%2FixTMC%2FoarUpqsLhpGg%3D

2.3 加载 BootstrapRegistryInitializer

this.bootstrapRegistryInitializers=new ArrayList(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));以上逻辑用于加载并初始化BootstrapRegistryInitializer及其相关类。

BootstrapRegistryInitializer是Spring Cloud Config的一个组件,其作用是在应用程序启动时初始化Spring Cloud Config客户端。

使用Spring Cloud Config,客户端通过向配置中心(配置服务器)发送请求来检索应用程序配置信息。 BootstrapRegistryInitializer负责向Spring容器注册配置中心相关信息。

由于篇幅限制,我们稍后将更详细地讨论BootstrapRegistryInitializer。

2.4 加载 ApplicationContextInitializer

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));以上代码用于加载并初始化ApplicationContextInitializer及其相关类。

ApplicationContextInitializer是Spring框架中的一个接口,其主要作用是在Spring容器更新之前初始化ConfigurableApplicationContext。这个接口的一个实现类可以被认为是一个回调函数,它的onApplicationEvent方法会在Spring容器启动时自动调用,从而允许开发者在容器更新之前执行一些自定义操作。

例如,此时您可能需要加载一些配置信息或预处理一些bean。您可以通过实现ApplicationContextInitializer接口并重写其onApplicationEvent方法来完成这些自定义需求。

由于篇幅限制,我们稍后将更详细地讨论ApplicationContextInitializer。

2.5 加载 ApplicationListener

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));上面的代码用于加载并初始化ApplicationListener及其关联的类。

ApplicationListener是Spring框架提供的事件监听机制,是Spring应用程序内部的事件驱动机制,通常用于监控应用程序的执行状态。其实现原理是观察者设计模式。这种设计模式的最初目的是提供系统业务逻辑之间的分离,从而提高系统的可扩展性和可维护性。

您可以自定义该类来实现ApplicationListener 接口并定义该类应监视的事件处理方法。当监听到的事件发生时,Spring会自动调用该方法来处理该事件。例如,在Spring Boot项目中,您可能需要在容器启动时执行某些操作,例如加载配置。这可以通过实现ApplicationListener 接口来实现。

由于篇幅限制,我们稍后将更详细地讨论ApplicationListener。

2.6 推断应用入口类

最后一步是通过调用SpringApplication的deduceMainApplicationClass方法来推断入口类。

私有类? deduceMainApplicationClass() { 尝试{ StackTraceElement[] stackTrace=new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ('main'.equals(stackTraceElement.getMethodName())) { 返回类。 forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { //此处捕获异常,继续执行后续逻辑} return null;}上面代码的思路如下。

首先,创建一个运行时异常并获取其堆栈数组。接下来,我们遍历数组来查看类方法是否包含main 方法。第一个匹配的类创建一个对象并通过Class.forName 方法返回它。最后,将上面创建的Class对象赋值给SpringApplication的成员变量mainApplicationClass。

总结

华子这篇文章初步了解了SpringApplication实例化流程。当然,由于篇幅有限,有些内容我们还不能详细解释。 Huazie 将在以后的博文中详细分析。

只有了解了Spring Boot 在启动时的行为方式,才能在后续练习中更深入地了解其工作机制,帮助您更快地识别和排查问题,让您的应用程序更轻松、更高效地连接到Spring Boot。

关注#HUAWEICloudDeveloperAlliance#,点击下方即可第一时间了解华为云新技术~

华为云博客_大数据博客_AI博客_云计算博客_开发者中心-华为云

版权声明:本文转载于网络,版权归作者所有。如有侵权,请联系本站编辑删除。

猜你喜欢