spring run启动流程
前言
写项目的时候不想知道Run()方法里面都干啥了么?那些Bean还有各种配置啥时候加载进去的?现在看来看一下。
一个SpringBoot
项目的根目录中最重要的一个类就是Application.java
,各自有各自的起名习惯,最主要的是这个类中标注了一个注解那就是@SpringBootApplication
,随后最重要的就是main
方法了。
1 |
|
Run()方法
- 点进去Run()方法看一下源码:
1 | public static void main(String[] args) { |
看到最后一行,一步一步看一下源码:
1、实例化SpringApplication
对象
1 | public SpringApplication(Class<?>... primarySources) { |
再根据上述代码一步一步看一下流程:
1、加载容器
1 | private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet", |
使用ClassUtils.isPresent()
对这三个类进行判断,看能不能加载,相当于一个过滤器,不然这里怎么会写出分叉逻辑,可以返回三个容器类型。
2、装配初始化器
了解 SpringBoot
自动装配知道有两处有spring.factories
,这里加载的是
1 | // 获取spring工厂实例 |
3、装配监听器
调用方式和上一步一致
4、加载主类
1 | this.mainApplicationClass = deduceMainApplicationClass(); |
到这儿,实例化
SpringApplication
对象结束,开始执行run()
方法。
2、执行Run()方法
看一下源码,这段有点长,但是没事,一步一步来
1 | public ConfigurableApplicationContext run(String... args) { |
1、设置headless
看一下这个方法干啥的,源码:
1 | private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless"; |
看到把这个全局静态变量设置为ture,那么“java.awt.headless”这个是干什么的?
这个属性是用来禁用图形功能。在一些服务器环境或无图形界面的操作系统上,启用图形功能可能会导致不必要的资源消耗和性能下降。因此,将 java.awt.headless 属性设置为 true 可以使得 Java 应用程序可以在无图形界面的环境中运行,降低功耗。
除了上面这种启动方式,也可以在application.yml中这样设置打开:
1 | java: |
2、启用SpringApplicationListener
1 | private SpringApplicationRunListeners getRunListeners(String[] args) { |
3、加载Banner
1 | private Banner printBanner(ConfigurableEnvironment environment) { |
3.1、图片Banner
1 | static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location"; |
spring.banner.image.location
属性用于指定自定义启动横幅(Banner)的图片文件位置。
1、可以将自定义的启动横幅图片放置在应用程序的src/main/resources/目录下,但必须是”gif”, “jpg”, “png” 中的一种:
src/main/resources/banner.png
2、 也可以放在yml文件中:
1
2
3
4
5
6# application.yml
spring:
banner:
image:
location: classpath:banner.png
这样,在启动 Spring Boot 应用程序时,控制台将显示指定位置的 banner.png 图片作为启动横幅,而不是默认的文本横幅。请确保图片文件存在,并且格式正确,以便正确显示在控制台中。
3.2、文本Banner
1 | static final String BANNER_LOCATION_PROPERTY = "spring.banner.location"; |
spring.banner.location
属性用于指定自定义启动横幅(Banner)的文本文件位置。
1、可以将自定义的启动横幅图片放置在应用程序的src/main/resources/目录下:
src/main/resources/custom-banner.txt
2、 也可以放在yml文件中:
1
2
3
4
5
6# application.yml
spring:
banner:
location: classpath:custom-banner.txt
banner.txt这个则是默认的文本,和上面的使用方式一样,只不过文件名字是:banner.txt。
4、异常报告类加载
加载spring.factories中的Erro Reporters
5、准备上下文
1 | private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { |
让我们逐行解释这段代码:
1、context.setEnvironment(environment);
:设置应用程序的上下文环境。environment
包含了应用程序的配置信息和属性。
2、postProcessApplicationContext(context);
:对应用程序的上下文进行后处理。这里可以进行一些定制化的操作,例如注册 Bean 或者添加自定义的配置。
3、applyInitializers(context);
:应用初始化器。上文我们说的加载的初始化器可以在此处被调用。
4、listeners.contextPrepared(context);
:通知所有的应用程序监听器,上下文已经被准备好了。这些监听器可以在应用程序的不同运行阶段做出操作。
5、刷新上下文
应用程序上下文会经历一系列的初始化和刷新过程,包括 Bean 的实例化、依赖注入、后置处理器的应用、事件发布等。这是整个应用程序上下文初始化的核心步骤,确保应用程序上下文得到正确初始化和配置。
6、系统上下文刷新完成后的监听器
listeners.started(context)
是用来通知应用程序启动监听器(ApplicationStartedEvent
监听器)的一个方法调用。它表示应用程序的上下文已经启动并刷新完成,在此时会触发 ApplicationStartedEvent
事件。
ApplicationStartedEvent
事件是一种通知机制,用于在系统特定时间点广播事件,允许其他组件或代码对这些事件进行响应。例如初始化某些组件、执行定时任务、发送通知等。我们自己也可以编写一个实现了 ApplicationListener<ApplicationStartedEvent>
接口的监听器,并在其中实现相应的逻辑。
以下是一个简单的示例,展示如何创建一个应用程序启动监听器并处理 ApplicationStartedEvent
事件:
1 | import org.springframework.boot.context.event.ApplicationStartedEvent; |
我创建了一个名为 MyApplicationStartedListener
的监听器,并实现了 ApplicationListener<ApplicationStartedEvent>
接口。在 onApplicationEvent
方法中,我们可以编写需要在应用程序启动后执行的逻辑。
当应用程序上下文启动并刷新完成时,Spring Boot 会自动触发 ApplicationStartedEvent
事件,而 MyApplicationStartedListener
监听器将接收到这个事件,并执行其中的逻辑。
8、执行自定义run方法
callRunners(context, applicationArguments)是用来调用应用程序的 ApplicationRunner
和 CommandLineRunner
的方法。
1 | private void callRunners(ApplicationContext context, ApplicationArguments args) { |
实现ApplicationRunner
和 CommandLineRunner
两个接口,用于在 Spring Boot 启动完成后执行一些自定义操作。例如加载初始化数据、执行定时任务、配置某些组件等。
但是这两个接口的作用略有不同:
ApplicationRunner
接口:定义了一个 run
方法,该方法在应用程序启动后被调用,传递一个 ApplicationArguments
对象,用于接收命令行参数。
CommandLineRunner
接口:也定义了一个 run
方法,该方法在应用程序启动后被调用,传递一个 String
数组,用于接收命令行参数。
在上述代码中,通过
1 | context.getBeansOfType(ApplicationRunner.class) |
分别获取了所有实现的 `ApplicationRunner` 和 `CommandLineRunner` 的类,然后对类进行排序,按照顺序执行这些实例的 `run` 方法。
下面举个例子:
假设有一个简单的学生管理系统。在应用程序启动时,我们希望自动向数据库中插入一些初始化的学生数据,并打印出命令行参数。
首先,我们需要创建一个学生实体类和一个学生数据初始化服务类。
1、学生实体类 Student.java
:
1 | public class Student { |
2、学生数据初始化服务类 StudentDataService.java
:
1 | @Service |
接下来,我们需要创建一个实现 ApplicationRunner
接口的初始化器,或者一个实现 CommandLineRunner
接口的初始化器。这里我都创建出来看一下,这两个初始化器会在应用程序启动时被调用,并执行相应的逻辑。
- 实现
ApplicationRunner
的初始化器StudentDataApplicationRunner.java
:
1 | @Component |
- 实现
CommandLineRunner
的初始化器CommandLineAppRunner.java
:
1 | @Component |
现在,当应用程序启动后,StudentDataApplicationRunner
和 CommandLineAppRunner
这两个初始化器会自动被调用。StudentDataApplicationRunner
会执行 StudentDataService
中的初始化数据逻辑,而 CommandLineAppRunner
则会打印命令行参数。
9、监听器
上文第二大点“执行run()方法”中,如果仔细看,源码中提到了三个监听器分别是:
1 | 1、listeners.starting(); |
是用来通知系统启动前的监听器(ApplicationStartingEvent 监听器)的一个方法调用。它表示系统即将开始启动,在此时会触发 ApplicationStartingEvent 事件。使用方法和上文第7小点一样哈,只不过要实现ApplicationListener
2、listeners.started(context);
是用来通知系统启动监听器(ApplicationStartedEvent 监听器)的一个方法调用。它表示系统的上下文已经启动并刷新完成,在此时会触发 ApplicationStartedEvent 事件。使用方法和上文第7小点一样哈,只不过要实现ApplicationListener
3、listeners.running(context);
是用来通知系统运行监听器(ApplicationRunningEvent 监听器)的一个方法调用。它表示系统的上下文已经启动并正在运行中,在此时会触发ApplicationRunningEvent事件。使用方法和上文第7小点一样哈,只不过要实现ApplicationListener
这三个监听器一定要区分开,是不一样的哦,它们都是可以扩展的,我们在系统不同的启动阶段可以做不同的监听器做自定义骚操作!
总结
这里我们就分析完了Run()方法的运行流程,在这里面有很多的可以自定义扩展的,下面的这些:ApplicationContextInitializer、ApplicationListener、banner、exceptionReporter、ApplicationRunner、CommandLineRunner,还有listeners.starting()、listeners.started(context)、listeners.running(context),这些都是可以自定义进行个性化扩展的,对这些都熟悉后,自己去对SpringBoot启动优化时间、DIY监听器,都是可以做的,分析不易,有帮助的话点个赞!
1. 启动Spring容器
- Spring容器启动的第一步是创建
ApplicationContext
对象。AplicationContext
是Spring容器的核心接口,代表了Spring上下文环境。常见的实现类有:ClassPathXmlApplicationContext
: 从类路径下的XML配置文件中加载Bean定义。AnnotationConfigApplicationContext
: 基于注解配置启动容器。FileSystemXmlApplicationContext
: 从文件系统中的XML配置文件中加载Bean定义。
2. 读取和解析配置文件
Spring需要根据配置(XML文件、注解配置类等)加载Bean定义。Spring的
BeanDefinitionReader
负责解析这些配置。- 如果使用XML配置文件,
XmlBeanDefinitionReader
负责读取和解析配置文件,并将解析得到的Bean定义信息转换为BeanDefinition
对象。 - 如果使用注解配置类,
AnnotatedBeanDefinitionReader
负责扫描和解析注解,并注册Bean。
- 如果使用XML配置文件,
3. BeanDefinition的注册
- 解析Bean定义后,Spring会将每个Bean的定义信息以
BeanDefinition
形式注册到BeanFactory
中。BeanDefinition
包含了Bean的各种元数据,比如Bean的类类型、作用域(单例/原型)、是否需要懒加载、依赖关系等。 - 通过
DefaultListableBeanFactory
来管理和存储这些BeanDefinition
。
4. Spring容器刷新(refresh())
这是Spring启动过程中最核心的步骤。
ApplicationContext
的refresh()
方法会完成一系列操作,启动和初始化Spring容器:
- 准备上下文环境:
- 设置上下文环境,包括配置资源(例如属性文件的读取),初始化一些特定的环境变量。
- 初始化BeanFactory:
- 创建或刷新
BeanFactory
,即完成对所有BeanDefinition
的解析和注册,形成一个完整的Bean定义列表。
- 创建或刷新
- BeanFactory的后处理:
- 如果有
BeanFactoryPostProcessor
(比如PropertyPlaceholderConfigurer
),Spring会在这个阶段执行它们的回调,修改BeanDefinition
的定义或者其他元数据。
- 如果有
- 注册Bean后处理器(BeanPostProcessor):
- 注册所有实现
BeanPostProcessor
接口的类。这些处理器允许在Bean初始化前后进行拦截处理(如AOP代理、依赖注入后的处理等)。
- 注册所有实现
- 实例化和初始化单例Bean:
- 对所有非懒加载的单例Bean进行实例化和初始化。这里涉及到Bean的生命周期方法的调用,包括依赖注入、初始化方法的调用。
- 初始化时会依次执行以下几个步骤:
- 调用Bean的构造方法实例化。
- 通过
set
方法注入依赖。 - 如果Bean实现了
BeanNameAware
、BeanFactoryAware
等接口,会调用相应的方法。 - 如果有
BeanPostProcessor
,会在Bean初始化前后进行拦截处理。 - 执行Bean的初始化方法(如
@PostConstruct
或自定义的init-method
)。
- 初始化事件广播器(ApplicationEventMulticaster):
- 创建并初始化事件广播器,用于处理应用事件,如
ContextRefreshedEvent
等。
- 创建并初始化事件广播器,用于处理应用事件,如
- 注册事件监听器:
- 注册应用程序中的事件监听器(如果有
ApplicationListener
),使得它们能够监听Spring容器发布的事件。
- 注册应用程序中的事件监听器(如果有
- 完成容器的初始化:
- 发布
ContextRefreshedEvent
,通知所有监听器Spring容器已经完全启动。
- 发布
- 准备上下文环境:
5. 获取Bean
- 当容器启动并完成初始化后,用户可以通过
ApplicationContext.getBean()
方法获取Bean的实例,使用已经准备好的Bean来处理业务逻辑。
6. 销毁与关闭容器
- 当应用程序结束或者容器关闭时,Spring会调用所有单例Bean的销毁方法,通常通过
close()
或destroy()
来关闭容器。 - 销毁过程包括:
- 调用所有实现了
DisposableBean
接口的Bean的destroy()
方法。 - 如果配置了自定义的销毁方法(如
destroy-method
),也会被执行。 - 发布
ContextClosedEvent
,通知监听器容器已经关闭。
- 调用所有实现了
1 | +-------------------+ |
主要方法:
refresh()
:Spring容器的核心方法,负责完成Bean的创建、初始化、后处理等工作。getBean()
:从Spring容器中获取Bean实例的主要接口。close()
:关闭Spring容器,销毁Bean并清理资源。
refresh()
1 | org.springframework.context.support.AbstractApplicationContext#refresh |
1 |
|
getBean()
1 | org.springframework.beans.factory.support.AbstractBeanFactory#getBean |
close()
总结:
Spring的启动流程围绕着容器的初始化和Bean的创建展开,通过读取配置文件、注册Bean定义、调用生命周期回调方法(如BeanPostProcessor
和BeanFactoryPostProcessor
)、初始化Bean及事件驱动等多个步骤,最终构建出一个完整的应用上下文