SpringBoot源码-spring boot启动入口ruan方法主线分析(一)

devtools/2024/11/29 4:06:55/

一、SpringBoot启动的入口

1.当我们启动一个SpringBoot项目的时候,入口程序就是main方法,而在main方法中就执行了一个run方法。

java">@SpringBootApplication
public class StartApp {public static void main(String[] args) {//  testSpringApplication.run(StartApp.class);}
}

2.SpringApplication.run方法

java">public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {// 调用重载的run方法,将传递的Class对象封装为了一个数组return run(new Class<?>[] { primarySource }, args);}

静态帮助器,可用于使用默认设置和用户提供的参数从指定的源运行{@link SpringApplication}

java">	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {// 创建了一个SpringApplication对象,并调用其run方法// 1.先看下构造方法中的逻辑// 2.然后再看run方法的逻辑return new SpringApplication(primarySources).run(args);}

3.SpringApplication构造器

java">创建一个新的{@link SpringApplication}实例。应用程序上下文将从指定的主源加载bean(参见{@link SpringApplication class-level}文档了解详细信息)。可以在调用之前自定义实例public SpringApplication(Class<?>... primarySources) {// 调用其他的构造方法this(null, primarySources);}

this(null, primarySources);方法进入:

java">	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {// 传递的resourceLoader为nullthis.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");// 记录主方法的配置类名称this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 记录当前项目的类型this.webApplicationType = WebApplicationType.deduceFromClasspath();// 加载配置在spring.factories文件中的ApplicationContextInitializer对应的类型并实例化// 并将加载的数据存储在了 initializers 成员变量中。setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 初始化监听器 并将加载的监听器实例对象存储在了listeners成员变量中setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 反推main方法所在的Class对象 并记录在了mainApplicationClass对象中this.mainApplicationClass = deduceMainApplicationClass();}
在本方法中完成了几个核心操作
  1. 推断当前项目的类型 this.webApplicationType是SERVLET;
  2. 加载配置在spring.factories文件中的ApplicationContextInitializer中的类型并实例化后存储在了initializers中。
    ApplicationContextInitializer
    getSpringFactoriesInstances(XXX.class)方法的作用是加载spring.factories文件中的kv对,后续获取XXX的对象从map中取不用在加载文件了。
  3. 和2的步骤差不多,完成监听器的初始化操作,并将实例化的监听器对象存储在了listeners成员变量中
  4. 通过StackTrace反推main方法所在的Class对象

4.SpringApplication的run方法:

java">public ConfigurableApplicationContext run(String... args) {// 创建一个任务执行观察器StopWatch stopWatch = new StopWatch();// 开始执行记录执行时间stopWatch.start();// 声明 ConfigurableApplicationContext 对象ConfigurableApplicationContext context = null;// 声明集合容器用来存储 SpringBootExceptionReporter 启动错误的回调接口Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();// 设置了一个名为java.awt.headless的系统属性// 其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.//对于服务器来说,是不需要显示器的,所以要这样设置.configureHeadlessProperty();// 获取 SpringApplicationRunListener 加载的是 EventPublishingRunListener// 获取启动时的监听器---》 事件发布器  发布相关事件的  11个监听器 谁去发布事件?SpringApplicationRunListeners listeners = getRunListeners(args);// 触发启动事件  发布 starting 事件 --》 那么监听starting事件的监听器就会触发listeners.starting();try {// 构造一个应用程序的参数持有类ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 创建并配置环境ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);// 配置需要忽略的BeanInfo信息configureIgnoreBeanInfo(environment);// 输出的Banner信息Banner printedBanner = printBanner(environment);// 创建应用上下文对象context = createApplicationContext();// 加载配置的启动异常处理器exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);// 刷新前操作prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 刷新应用上下文 完成Spring容器的初始化refreshContext(context);// 刷新后操作afterRefresh(context, applicationArguments);// 结束记录启动时间stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}// 事件广播 启动完成了listeners.started(context);callRunners(context, applicationArguments);}catch (Throwable ex) {// 事件广播启动出错了handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {// 监听器运行中listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}// 返回上下文对象--> Spring容器对象return context;}

在这个方法中完成了SpringBoot项目启动的很多核心的操作,总结下上面的步骤

  1. 创建了一个任务执行的观察器,统计启动的时间
  2. 声明ConfigurableApplicationContext对象
  3. 声明集合容器来存储SpringBootExceptionReporter即启动错误的回调接口
  4. 设置java.awt.headless的系统属性
  5. 获取我们之间初始化的监听器(EventPublishingRunListener),并触发starting事件
  6. 创建ApplicationArguments这是一个应用程序的参数持有类
  7. 创建ConfigurableEnvironment这时一个配置环境的对象
  8. 配置需要忽略的BeanInfo信息
  9. 配置Banner信息对象
  10. 创建对象的上下文对象
  11. 加载配置的启动异常的回调异常处理器
  12. 刷新应用上下文,本质就是完成Spring容器的初始化操作
  13. 启动结束记录启动耗时
  14. 完成对应的事件广播
  15. 返回应用上下文对象。

二、SpringApplication的run方法详解

对上面的方法进行解析:
1.创建了一个任务执行的观察器,统计启动的时间
StopWatch调用构造方法进行实例化,以及调用start方法:

java">启动一个命名任务。如果在不首先调用此方法的情况下调用stop()或计时方法,则结果是未定义的。参数:taskName—要启动的任务的名称public void start(String taskName) throws IllegalStateException {if (this.currentTaskName != null) {throw new IllegalStateException("Can't start StopWatch: it's already running");}this.currentTaskName = taskName;this.startTimeNanos = System.nanoTime();}

2. 声明ConfigurableApplicationContext对象

java">		// 声明 ConfigurableApplicationContext 对象ConfigurableApplicationContext context = null;

3. 声明集合容器来存储SpringBootExceptionReporter即启动错误的回调接口

java">		// 声明集合容器用来存储 SpringBootExceptionReporter 启动错误的回调接口Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

4. 设置java.awt.headless的系统属性
// 设置了一个名为java.awt.headless的系统属性
// 其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.
//对于服务器来说,是不需要显示器的,所以要这样设置.

java">private void configureHeadlessProperty() {System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));}

5. 获取我们之间初始化的监听器(EventPublishingRunListener),并触发starting事件
SpringBoot监听事件相关访问上一篇

6. 创建ApplicationArguments这是一个应用程序的参数持有类
解析启动的时候传递的–开头的参数

java">			// 构造一个应用程序的参数持有类ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

DefaultApplicationArguments构造方法如下:

java">public DefaultApplicationArguments(String... args) {Assert.notNull(args, "Args must not be null");this.source = new Source(args);this.args = args;}
java">Source(String[] args) {super(args);}

点击super

java">public SimpleCommandLinePropertySource(String... args) {super(new SimpleCommandLineArgsParser().parse(args));}

点击parse:在命令行中启动Spring Boot应用时,可以使用–后跟系统属性来传递参数。例如:
java -jar yourapp.jar --server.port=8081
根据上述规则解析给定的String数组,返回一个完全填充的CommandLineArgs对象。Params: args -命令行参数,通常来自main()方法

java">public CommandLineArgs parse(String... args) {CommandLineArgs commandLineArgs = new CommandLineArgs();for (String arg : args) {if (arg.startsWith("--")) {String optionText = arg.substring(2);String optionName;String optionValue = null;int indexOfEqualsSign = optionText.indexOf('=');if (indexOfEqualsSign > -1) {optionName = optionText.substring(0, indexOfEqualsSign);optionValue = optionText.substring(indexOfEqualsSign + 1);}else {optionName = optionText;}if (optionName.isEmpty()) {throw new IllegalArgumentException("Invalid argument syntax: " + arg);}commandLineArgs.addOptionArg(optionName, optionValue);}else {commandLineArgs.addNonOptionArg(arg);}}return commandLineArgs;}

7. 创建ConfigurableEnvironment这时一个配置环境的对象

java">	// 创建并配置环境ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

spring boot启动时配置properties和yml文件解析过程以及spring.profiles.active多环境下配置文件的解析
这个方法的细节看之前发的这个贴子,里面讲解了Spring Boot在启动过程中加载properties和yaml文件的细节处理过程。

8.配置需要忽略的BeanInfo信息

java">			// 配置需要忽略的BeanInfo信息configureIgnoreBeanInfo(environment);
java">	private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());}}

9.配置Banner信息对象

java">			// 输出的Banner信息Banner printedBanner = printBanner(environment);

printBanner方法主要实现的就是将自己配置的banner.txt或者图片打印输出到控制台或者日志文件中,如果用户没有自定义输出,就采用默认的输出一个springboot的logo标志,下面的连接详细讲解了这部分的内容。
Spring Boot启动时控制台为何会打印logo以及自定义banner.txt文件控制台打印

10.创建对象的上下文对象

java">			// 创建应用上下文对象context = createApplicationContext();

createApplicationContext的源码为:
SpringApplication的构造方法中有得到webApplicationType的类型是SERVLET

java">	protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch (this.webApplicationType) {case SERVLET:contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);break;case REACTIVE:contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);break;default:contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);}}catch (ClassNotFoundException ex) {throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);}}return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);}

所以contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);

String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = “org.springframework.boot.”
+ “web.servlet.context.AnnotationConfigServletWebServerApplicationContext”;

(ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);是对AnnotationConfigServletWebServerApplicationContext进行实例化,所以看下这个方法的构造方法。

java">	public AnnotationConfigServletWebServerApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);}

继续AnnotatedBeanDefinitionReader构造

java">public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {this(registry, getOrCreateEnvironment(registry));}

继续this(registry, getOrCreateEnvironment(registry));

java">	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");Assert.notNull(environment, "Environment must not be null");this.registry = registry;this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}

AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);细节如下:

java">	/*** Register all relevant annotation post processors in the given registry.* @param registry the registry to operate on*/public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {registerAnnotationConfigProcessors(registry, null);}

registerAnnotationConfigProcessors会把一些internal开头的路径名对应的类名注册到befactory的beanDefinitionMap和beanDefinitionNames中

java">org.springframework.context.annotation.internalConfigurationAnnotationProcessor---------> ConfigurationClassPostProcessororg.springframework.context.annotation.internalAutowiredAnnotationProcessor---------> AutowiredAnnotationBeanPostProcessororg.springframework.context.annotation.internalCommonAnnotationProcessor ---------> 
CommonAnnotationBeanPostProcessororg.springframework.context.event.internalEventListenerProcessor---------> 
EventListenerMethodProcessororg.springframework.context.event.internalEventListenerFactory---------> 
DefaultEventListenerFactory

在这里插入图片描述
registerAnnotationConfigProcessors执行结束之后,
在这里插入图片描述
ConfigurationClassPostProcessor的类图如下:
在这里插入图片描述
之前在Spring源码的refresh方法中有讲过这个类,主要完成自动装配注解的扫面以及其他相关注解修饰的类生成bean定义信息放到befactory中,交给spring管理其生命周期。

11.加载配置的启动异常的回调异常处理器

java">			// 加载配置的启动异常处理器exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);

spring.factories文件SpringBootExceptionReporter有一个:

java">org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

getSpringFactoriesInstances会把这个类进行实例化返回给exceptionReporters。


http://www.ppmy.cn/devtools/137827.html

相关文章

python excel接口自动化测试框架!

今天采用Excel继续写一个接口自动化测试框架。 设计流程图 这张图是我的excel接口测试框架的一些设计思路。 首先读取excel文件&#xff0c;得到测试信息&#xff0c;然后通过封装的requests方法&#xff0c;用unittest进行测试。 其中&#xff0c;接口关联的参数通过正则进…

c++ 主函数里的return 0写不写的区别是什么?

在 C 中&#xff0c;main 函数是程序的入口点。main 函数的标准定义如下&#xff1a; int main() {// ... 代码 ... } 或者可以带参数&#xff1a; int main(int argc, char* argv[]) {// ... 代码 ... } main 函数的返回类型是 int&#xff0c;这意味着它应该返回一个整数…

缓存方案分享

不知道大家平常更新缓存是怎么做的&#xff0c;但是大部分时候都是更新数据的同时更新缓存&#xff0c;今天和同事一起聊到一个缓存方案的问题&#xff0c;感觉很有趣、非常精妙&#xff0c;记录一下。 基于此本文将介绍几种常见的缓存更新策略&#xff0c;包括简单的缓存覆盖…

C/C++基础知识复习(31)

1) 什么是 C 中的多继承&#xff1f;它有哪些优缺点&#xff1f; 多继承&#xff08;Multiple Inheritance&#xff09;是指在 C 中&#xff0c;一个类可以继承自多个基类&#xff0c;从而拥有多个基类的特性和行为。具体来说&#xff0c;子类可以通过继承多个父类&#xff0c…

海康VsionMaster学习笔记(学习工具+思路)

一、前言 VisionMaster算法平台集成机器视觉多种算法组件&#xff0c;适用多种应用场景&#xff0c;可快速组合算法&#xff0c;实现对工件或被测物的查找测量与缺陷检测等。VM算法平台依托海康威视在图像领域多年的技术积淀&#xff0c;自带强大的视觉分析工具库&#xff0c;可…

简易记事本前端开发(初步)

目录 简介 1. 创建Vue3项目和导入依赖 创建Vue3项目 安装依赖 2. 登录注册页面的编写 登录页面 注册页面 3. 首页仪表盘的编写 4. 事件分类的编写 5. 事件管理的编写 结语 简介 在本篇博客中&#xff0c;我们将一起探索如何使用Vue3框架&#xff0c;结合ECharts图表…

【GAMES101笔记速查——Lecture 21 Animation】

动画/模拟 目录 1 计算机动画历史 1.1 动画是什么&#xff1f; 1.2 动画的历史 第一个动画&#xff1a;公元前&#xff0c;原始壁画 1813年&#xff1a;圆盘画图&#xff0c;按一定速度显示 1878年&#xff1a;第一个电影&#xff1a;一开始是科学研究用&#xff0c;比如…

【NLP 1、人工智能与NLP简介】

人人都不看好你&#xff0c;可偏偏你最争气 —— 24.11.26 一、AI和NLP的基本介绍 1.人工智能发展流程 弱人工智能 ——> 强人工智能 ——> 超人工智能 ① 弱人工智能 人工智能算法只能在限定领域解决特定的问题 eg&#xff1a;特定场景下的文本分类、垂直领域下的对…