springboot启动流程 (1) 流程概览

news2024/11/24 8:45:23

本文将通过阅读源码方式分析SpringBoot应用的启动流程,不涉及Spring启动部分(有相应的文章介绍)。

本文不会对各个流程做展开分析,后续会有文章介绍详细流程。

SpringApplication类

应用启动入口

使用以下方式启动一个SpringBoot应用:

@SpringBootApplication
public class SpringBootDemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringBootDemoApplication.class, args);
  }
}

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);
}

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		// 获取应用env
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		configureIgnoreBeanInfo(environment);
		// 打印banner
		Banner printedBanner = printBanner(environment);
		// 创建ApplicationContext
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		// 一些准备工作
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		// refresh ApplicationContext
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		// 调用listener
		listeners.started(context);
		// 调用ApplicationRunner和CommandLineRunner
		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);
	}
	return context;
}

获取应用env

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// 创建StandardServletEnvironment, 会初始化四个PropertySource:
	// servletConfigInitParams, servletContextInitParams, systemProperties, systemEnvironment
	// 比如-Dserver.port=8888会在systemProperties中
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	// 添加defaultProperties和命令行配置参数即CommandLinePropertySource
	// 通常都没有这两个配置
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// 使用ConfigurationPropertySourcesPropertySource封装并暴露所有的PropertySource集
	ConfigurationPropertySources.attach(environment);
	// 添加ApplicationEnvironmentPreparedEvent事件并触发multicastEvent加载应用配置文件
	listeners.environmentPrepared(environment);
	// 将spring.main.xx配置加载到SpringApplication对象
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}

加载配置文件的入口在ConfigFileApplicationListener类中:

public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
	addPropertySources(environment, application.getResourceLoader());
}

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	// 添加RandomValuePropertySource
	RandomValuePropertySource.addToEnvironment(environment);
	// 加载配置文件
	new Loader(environment, resourceLoader).load();
}

加载配置文件的源码较多,此处不做记录,简单梳理一下流程:

  1. 加载active profile配置文件
    • 如果配置了spring.config.additional-location或spring.config.location参数,会使用它们作为配置文件。如果这两个参数值是目录,则会从这两个目录下查找配置文件
    • 默认从classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/目录下查找application-xx.properties或application-xx.yml文件
    • 使用PropertiesPropertySourceLoader和YamlPropertySourceLoader解析配置文件
    • 会将配置参数封装成OriginTrackedMapPropertySource类型对象,使用applicationConfig: [classpath:/application-dev.yml]之类的字符串作为PropertySource的名称
  2. 加载默认的application.properties或application.yml文件
  3. 解析出来的所有PropertySource都会添加到environment的propertySources中,propertySources是一个MutablePropertySources对象,管理着所有的PropertySource集,在这个过程中,添加的先后顺序决定了配置的优先级

创建ApplicationContext

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

在这里插入图片描述

在这里插入图片描述

prepareContext

private void prepareContext(
		ConfigurableApplicationContext context, ConfigurableEnvironment environment,
		SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments,
		Banner printedBanner) {
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	// Apply any ApplicationContextInitializers to the context before it is refreshed.
	// ApplicationContextInitializers集是在创建SpringApplication对象的时候初始化的
	applyInitializers(context);
	// 触发contextPrepared事件
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// 获取BeanFactory并注册必要的Bean
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	// 注册启动参数
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	// 注册banner printer
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	// 设置是否允许Bean覆盖,使用spring.main.allowBeanDefinitionOverriding参数配置
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	if (this.lazyInitialization) {
		context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	// Load the sources
	Set<Object> sources = getAllSources();
	// 将SpringApplication.run(Xxx.class, args)方法传入的Class注册到容器
	// 使用AnnotatedBeanDefinitionReader.register(Class<?>...)方法注册启动类
	load(context, sources.toArray(new Object[0]));
	// 触发contextLoaded事件
	listeners.contextLoaded(context);
}

refreshApplicationContext

protected void refresh(ConfigurableApplicationContext applicationContext) {
	applicationContext.refresh();
}

调用的是ServletWebServerApplicationContext的refresh方法:

public final void refresh() throws BeansException, IllegalStateException {
	try {
		super.refresh();
	} catch (RuntimeException ex) {
		// 关闭web server
		WebServer webServer = this.webServer;
		if (webServer != null) {
			webServer.stop();
		}
		throw ex;
	}
}

绝大多数的refresh逻辑都在AbstractApplicationContext类里面,ServletWebServerApplicationContext中会在onRefresh阶段创建webServer:

protected void onRefresh() {
	super.onRefresh();
	try {
		createWebServer();
	} catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}

调用ApplicationRunner和CommandLineRunner

private void callRunners(ApplicationContext context, ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();
	runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
	runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
	AnnotationAwareOrderComparator.sort(runners);
	for (Object runner : new LinkedHashSet<>(runners)) {
		if (runner instanceof ApplicationRunner) {
			callRunner((ApplicationRunner) runner, args);
		}
		if (runner instanceof CommandLineRunner) {
			callRunner((CommandLineRunner) runner, args);
		}
	}
}

SpringBootApplication注解

指示一个配置类,该类声明一个或多个@Bean方法,并触发自动配置和组件扫描。这是一个方便的注解,相当于声明@Configuration、@EnableAutoConfiguration和@ComponentScan注解。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	/**
	 * Base packages to scan for annotated components. Use scanBasePackageClasses
	 * for a type-safe alternative to String-based package names.
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * Type-safe alternative to scanBasePackages for specifying the packages to
	 * scan for annotated components. The package of each class specified will be scanned.
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	/**
	 * The BeanNameGenerator class to be used for naming detected components
	 * within the Spring container.
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	/**
	 * Specify whether @Bean methods should get proxied in order to enforce
	 * bean lifecycle behavior, e.g. to return shared singleton bean instances even in
	 * case of direct @Bean method calls in user code. This feature requires
	 * method interception, implemented through a runtime-generated CGLIB subclass which
	 * comes with limitations such as the configuration class and its methods not being
	 * allowed to declare final.
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

SpringBootConfiguration注解

指示一个类提供Spring Boot application @Configuration功能。可以替代Spring的标准@Configuration注解,以便可以自动找到配置类。

应用程序应该只标注一个@SpringBootConfiguration,大多数SpringBoot应用程序将从@SpringBootApplication继承它。

@Configuration
public @interface SpringBootConfiguration {

	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

EnableAutoConfiguration注解

启用SpringBoot自动装配功能,尝试猜测和配置可能需要的组件Bean。

自动装配类通常是根据类路径和定义的Bean来应用的。例如,如果类路径上有tomcat-embedded.jar,那么可能需要一个TomcatServletWebServerFactory(除非已经定义了自己的Servlet WebServerFactory Bean)。

自动装配试图尽可能地智能化,并将随着开发者定义自己的配置而取消自动装配相冲突的配置。开发者可以使用exclude()排除不想使用的配置,也可以通过spring.autoconfig.exclude属性排除这些配置。自动装配总是在用户定义的Bean注册之后应用。

用@EnableAutoConfiguration注解标注的类所在包具有特定的意义,通常用作默认扫描的包。通常建议将@EnableAutoConfiguration(如果没有使用@SpringBootApplication注解)放在根包中,以便可以搜索所有子包和类。

自动装配类是普通的Spring @Configuration类,使用SpringFactoriesLoader机制定位。通常使用@Conditional方式装配,最常用的是@ConditionalOnClass和@ConditionalOnMissingBean注解。

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * 当类路径下没有指定的类时,可以使用这个属性指定排除的类
	 */
	String[] excludeName() default {};
}

AutoConfigurationPackage注解

Registers packages with AutoConfigurationPackages. When no base packages or base package classes are specified, the package of the annotated class is registered.

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};
}

AutoConfigurationImportSelector类

DeferredImportSelector接口的实现类,处理自动装配,导出所有需要自动装配的类。

创建WebServer

SpringBoot会在onRefresh阶段创建webServer,首先从spring容器获取ServletWebServerFactory,然后调用getWebServer方法创建webServer。

getWebServer方法需要传入ServletContextInitializer集来初始化ServletContext。

@FunctionalInterface
public interface ServletContextInitializer {

	void onStartup(ServletContext servletContext) throws ServletException;
}

我们开发者如果需要使用ServletContextInitializer来初始化ServletContext的话,也可以编写一个实现类,然后将其注册到spring容器即可。

另外,SpringBoot还会自动装配DispatcherServletAutoConfiguration类,这个类会创建DispatcherServlet和DispatcherServletRegistrationBean。DispatcherServlet是SpringWebMvc的最核心组件,DispatcherServletRegistrationBean实现了ServletContextInitializer接口,可以将DispatcherServlet注册到ServletContext。以TomcatServletWebServerFactory为例,这个类会通过TomcatStarter来调用所有的ServletContextInitializer,TomcatStarter实现了ServletContainerInitializer接口,Tomcat的ServletContext在启动阶段会调用ServletContainerInitializer的onStartup方法来初始化Servlet容器。

SpringBoot启动流程

  • 初始化environment应用配置参数:servletConfigInitParams, servletContextInitParams, systemProperties, systemEnvironment及配置文件等
  • 创建ApplicationContext对象,SpringBoot应用默认使用的是AnnotationConfigServletWebServerApplicationContext类
  • prepareContext阶段:触发一些事件,将启动类注册到Spring容器
  • refresh阶段:扫描应用组件,自动装配
  • onRefresh阶段:创建并初始化WebServer
  • 调用ApplicationRunner和CommandLineRunner

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/648578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机视觉】最近跑实验的感悟:大模型训练太难了!

文章目录 一、大模型训练太难了二、大模型的训练有三大难点三、OpenAI 的一些启发 一、大模型训练太难了 这里大模型训练特指基座大模型的从0开始训练&#xff0c;不包括在2000条数据上SFT这样的小任务。 有人说大模型嘛&#xff0c;简单&#xff0c;给我卡就行&#xff0c;等…

「2023」高频前端面试题汇总之JavaScript篇(上)

近期整理了一下高频的前端面试题&#xff0c;分享给大家一起来学习。如有问题&#xff0c;欢迎指正&#xff01; 一、数据类型 1. JavaScript有哪些数据类型&#xff0c;它们的区别&#xff1f; JavaScript共有八种数据类型&#xff0c;分别是 Undefined、Null、Boolean、N…

【JVM系列】内存分配与回收策略详解

文章目录 Minor GC 和 Full GC内存分配策略一般过程第一次轻GC第二次轻GC第N次GC 特殊过程小结 Full GC 的触发条件1. 调用 System.gc()2. 老年代空间不足3. 空间分配担保失败4. JDK 1.7 及以前的永久代空间不足5. Concurrent Mode Failure Minor GC 和 Full GC Minor GC&#…

基于DDD实现的用户注册流程,很优雅!

欢迎回来&#xff0c;我是飘渺。今天继续更新DDD&微服务的系列文章。 在前面的文章中&#xff0c;我们深入探讨了DDD的核心概念。我理解&#xff0c;对于初次接触这些概念的你来说&#xff0c;可能难以一次性完全记住。但别担心&#xff0c;学习DDD并不仅仅是理论的理解&am…

6.SpringCloudAlibaba 整合 Sentinel

一、分布式系统遇到的问题 1 服务雪崩效应 在分布式系统中&#xff0c;由于网络原因或自身的原因&#xff0c;服务一般无法保证 100%是可用的。如果一个服务出现了问题&#xff0c;调用这个服务就会出现线程阻塞的情况&#xff0c;此时若有大量的请求涌入&#xff0c;就会出现…

4.11 socket地址 4.12 IP地址转换函数 4.13TCP通信流程 4.14socket函数

4.11 socket地址 socket地址其实是一个结构体&#xff0c;封装端口号和IP等信息。后面的socket相关的api中需要使用到这个socket地址。 客户端 -> 服务器&#xff08;IP, Port&#xff09; 通用 socket地址 socket 网络编程接口中表示 socket 地址的是结构体 sockaddr&am…

安装Vue(重点笔记)

目录 什么是Vue? 特点 Node.js安装 Vue安装 1、安装Vue.js 1.1&#xff09;安装失败解决 1.2&#xff09;安装成功 2、安装webpack模板 3、安装脚手架 4、安装vue-router 创建第一个Vue-cli应用程序 1、命令行(cmd) cd 到指定的目录 2. 创建第一个基于webpack模…

【C#】WinForm中如何获取一个控件相对于主界面的位置

文章目录 前言一、新建WinForm程序二、效果与代码总结 前言 使用button控件的 PointToScreen 方法和Form控件的 PointToClient 方法来获取button1相对于Form边界的位置。具体步骤如下&#xff1a; 获取button1在屏幕上的位置&#xff1a; Point button1ScreenPos button1.P…

【TensorRT】TensorRT的环境配置

本文主要记录TensorRT8.6的环境配置过程&#xff01; 官方文档&#xff1a;NVIDIA TensorRT - NVIDIA Docs TensorRT相关版本的文档&#xff1a; Documentation Archives :: NVIDIA Deep Learning TensorRT Documentation 一 、下载CUDA和cudann CUDA下载&#xff1a;CUDA T…

最近不知道写啥,打算整理点儿关于钱币的文章,也转载点儿别人的技术文章,毕竟,洒家还是干技术滴嘛...

最近开始整理之前陆陆续续买的乱七八糟的东西了&#xff0c;现在一堆东西还乱扔着呢~~ 先整理了一套大五帝钱还有下面这套清五帝。 清五帝钱一套: 27宝泉局顺治通宝&#xff0c;铁壳锈 27.5康熙通宝满汉同 26.5宝泉局雍正通宝 三离划 26&#xff08;卡&#xff09;宝云局乾隆通…

Vision Transformer综述 总篇

Vision Transformer综述 1. Transformer简介2. Transformer组成2.1 Self-AttentionMulti-Head Attention&#xff08;多头注意力&#xff09; 2.2 Transformer的其他关键概念2.2.1 Feed-Forward Network 前馈网络2.2.2 Residual Connection 残差连接2.2.3 解码器中的最后一层 3…

Go GC:了解便利背后的开销

1. 简介 当今&#xff0c;移动互联网和人工智能的快(越)速(来)发(越)展(卷)&#xff0c;对编程语言的高效性和便利性提出了更高的要求。Go作为一门高效、简洁、易于学习的编程语言&#xff0c;受到了越来越多开发者的青睐。 Go语言的垃圾回收机制&#xff08;Garbage Collectio…

client-go的Indexer三部曲之一:基本功能

关于《client-go的Indexer三部曲》系列 该系列是《client-go实战系列》的子系列文章&#xff0c;共三篇内容&#xff0c;分别从功能、性能、源码三个角度对client-go内部的Indexer组件进行说明&#xff0c;目标是与大家一同学习Indexer&#xff0c;并掌握如何在开发中通过Inde…

EBU5476 Microprocessor System Design 知识点总结_6 Serial Communication

Serial Communication 串口通信&#xff0c;一种发送消息的通信方式。 串&#xff0c;指的是发数据的方式&#xff1a;一位一位串行发&#xff0c;并行是可能有多路通道&#xff0c;每路同时发一个数据&#xff0c;多路同时到达。 串口通信有单工 Simplex&#xff0c;半双工…

基于SpringBoot+Vue的乐校园二手书交易管理系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

35. 应用监控【监控端点健康信息】

1、展示健康信息详情 开发者可以通过查看健康信息来获取应用的运行数据&#xff0c;进而提早发现应用问题&#xff0c;提早解决&#xff0c; 免造成损失。默认情况下开发者只能获取 status 信息&#xff08;见图 1 &#xff09;&#xff0c;这是因为 detail 信息默认不显示&…

【Java常见面试题】Spring篇

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线常见面试题 目录 1、简单介绍Spring 2、说说你对IOC的理解 3、说说你对AOP的理解 4、说说Bean的生命周期 5、说说循环依赖和三级缓存 6、说说Bean的几种…

DataV图表-排名轮播表自定义

DataV图表-排名轮播表自定义数据大屏可视化 场景&#xff1a;需要计算根据分数不同柱子的颜色不同 低于60分变成为橙色柱子 一开始使用的是 dv-scroll-ranking-board 这个不可以自定义颜色和属性 我们可以更改 dv-scroll-board 样式来实现 排名轮播表 安装 data-view npm ins…

如何使用Leangoo领歌管理敏捷缺陷

缺陷管理通常关注如下几个方面&#xff1a; 1. 缺陷的处理速度 2. 缺陷处理的状态 3. 缺陷的分布 4. 缺陷产生的原因 使用​​​​​​​Leangoo领歌敏捷工具​​​​​​​我们可以对缺陷进行可视化的管理&#xff0c;方便我们对缺陷的处理进展、负责人、当前状态、分布情…

『 MySQL篇 』:MySQL 锁机制介绍

目录 一. 概述 二. 全局锁 三 . 表级锁 三. 行级锁 一. 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据…