SpringBoot源码分析之Tomcat是如何在SpringBoot中启动的?

news2024/12/27 2:11:21

一.前言

我们知道SpringBoot可以直接把传统的war包打成可执行的jar包,直接启动。这得益于SpringBoot内置了容器可以直接启动。本文将以 Tomcat 为例,来看看 SpringBoot 是如何启动 Tomcat 的。

二.源码分析

一.SpringApplication初始化

public class SpringBootDemoApplication {

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

    }

}

调用到最终的run方法我们来看一下

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

这里面首先创建了一个SpringApplication的对象,然后调用了run方法,我们先看在创建对象的死后做了什么

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

这里面对于我们有用的是WebApplicationType.deduceFromClasspath()方法,因为他返回了一个叫webApplicationType的东西。这个我们一会能用得到。看一下deduceFromClasspath()的源码

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

其中最主要的方法是ClassUtils.isPresent方法

	public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
		try {
			forName(className, classLoader);
			return true;
		}
		catch (IllegalAccessError err) {
			throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
					className + "]: " + err.getMessage(), err);
		}
		catch (Throwable ex) {
			// Typically ClassNotFoundException or NoClassDefFoundError...
			return false;
		}
	}

可以看到就是利用反射来判断传入的className是否存在,如果不存在返回false.

最后this.webApplicationType= WebApplicationType.SERVLET这个东东。

至于其他忽略的源码,之后会在其他源码分析模块讲解

二.run方法的调用

	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);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			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);
		}
		return context;
	}

这里面对于我们相关的有两个方法一个是context = createApplicationContext();方法,另一个是refreshContext(context)方法,我们分别分析一下源码。

(1)createApplicationContext()

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

还记得我们上一部分返回的this.webApplicationType的值是什么么?是Servlet对吧。

那么返回的contextClass就是对应的DEFAULT_SERVLET_WEB_CONTEXT_CLASS值也就是

public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

这里就是根据我们的 webApplicationType 来判断创建哪种类型的 Servlet,代码中分别对应着 Web 类型(SERVLET),响应式 Web 类型(REACTIVE),非 Web 类型(default),我们建立的是 Web 类型,所以肯定实例化 DEFAULT_SERVLET_WEB_CONTEXT_CLASS 指定的类,也就是 AnnotationConfigServletWebServerApplicationContext 类,我们来用图来说明下这个类的关系

我们看一下返回回来的ConfigurableApplicationContext 发现他是一个接口。因为我们是AnnotationConfigServletWebServerApplicationContext。是他的实现类,所以我们自然而然进入到AnnotationConfigServletWebServerApplicationContext中

 这是他的类的关系 可以发现最后继承的是AbstractApplicationContext。至此创建线程上线文完成。回过头我们来看一下refreshContext()方法

(2)refreshContext()方法

我们跟随代码来到

	private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

这块还是调用的本方法的refresh方法最后强转成AbstractApplicationContext,调用它的refresh()方法 

	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}
public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

这里我们看到 onRefresh()方法是调用其子类的实现,根据我们上文的分析,我们这里的子类是 ServletWebServerApplicationContext。

调用的是这个ServletWebServerApplicationContext类的onRefresh()方法()

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

关键代码在于createWebServer();

	private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			ServletWebServerFactory factory = getWebServerFactory();
			this.webServer = factory.getWebServer(getSelfInitializer());
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context", ex);
			}
		}
		initPropertySources();
	}

这就是创建WebServer的意思,其中最重要的就是通过getWebServerFactory()方法来获取webserver,我们来看一下。

 看见了么?这个工厂接口有下面这几种实现类,根据上图我们发现,工厂类是一个接口,各个具体服务的实现是由各个子类来实现的,所以我们就去看看 TomcatServletWebServerFactory.getWebServer()的实现。

	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}

总结

SpringBoot 的启动是通过 new SpringApplication()实例来启动的,启动过程主要做如下几件事情:> 1. 配置属性 > 2. 获取监听器,发布应用开始启动事件 > 3. 初始化输入参数 > 4. 配置环境,输出 banner > 5. 创建上下文 > 6. 预处理上下文 > 7. 刷新上下文 > 8. 再刷新上下文 > 9. 发布应用已经启动事件 > 10. 发布应用启动完成事件

而启动 Tomcat 就是在第 7 步中“刷新上下文”;

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

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

相关文章

【STC8】热启动串口指令下载

前言 在目标开发板没有装载自动下载电路的时候&#xff0c;往往需要冷启动&#xff0c;也就是需要手动开关电源&#xff0c;来达到单片机复位下载。当然还有一种方法是热启动&#xff0c;通过串口接收到自定义的指令后&#xff0c;软件执行复位下载。这就是本文介绍的内容。 材…

理解神经网络的数学原理(四)多层感知机(MLP)与二叉空间分割树(BSP Tree)

概述 最近发现了一个传统算法&#xff0c;非常适合描述多层感知机&#xff08;Multi-Layer Perceptron&#xff0c;MLP&#xff09;的模型逻辑&#xff0c;其算法逻辑也非常清晰简单&#xff0c;所以在这里再对比一下&#xff0c;方便大家更容易理解多层感知机的内容&#xff…

手写Docker之认识NameSpace、CGroups、Union file system

关于NameSpace Linux NameSpace主要是kernel中用于隔离系统资源的功能&#xff0c;而docker就是基于NameSpace去隔离系统资源达到容器化的效果 以下案例均以该代码进行举例&#xff1a; package mainimport ("fmt""os""os/exec""syscall&…

【C++】mapsetmultimapmultiset使用说明

文章目录 关联式容器键值对pair的定义(键值对) 树形结构的关联式容器Set -> 排序去重Set的文档介绍Set的使用:set的构造set的迭代器set的容量set修改操作API接口总结: multiset -> 排序 可重复lower_bound&&upper_bound mapmap的模板参数说明map的构造map的迭代…

毕业季,各互联网大厂急缺这类 Python 人才!

又快到了应届毕业生找工作的季节了&#xff0c;一大波大厂高薪岗位陆续开放&#xff0c;想拿 Offer、升职加薪的你准备得怎么样了&#xff1f; 今年的招聘力度可以说是近几年最大&#xff0c;比如字节跳动旗下的大力教育&#xff0c;高调宣布&#xff1a;“未来 4 个月&#x…

最新整理Java面试八股文,大厂必备神器

在看这篇文章之前&#xff0c;我想我们需要先搞明白八股文是什么&#xff1f;&#xff1f;&#xff1f; 明清科举考试的一种文体&#xff0c;也称制义、制艺、时文、八比文。八股文章就四书五经取题&#xff0c;内容必须用古人的语气&#xff0c;绝对不允许自由发挥&#xff0…

记录--axios和loading不得不说的故事

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 loading的展示和取消可以说是每个前端对接口的时候都要关心的一个问题。这篇文章将要帮你解决的就是如何结合axios更加简洁的处理loading展示与取消的逻辑。 首先在我们平时处理业务的时候loading一般…

OpenAI 重磅发布 ChatGPT iOS 客户端!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 今天凌晨&#xff0c;OpenAI 正式发布了 iOS 客户端&#xff01; 这代表你可以直接在 iPhone 和 iPad 上直接使用 ChatGPT 进行聊天了。 该客户端基于 Whisper 开源模型&#xff0c;集成了…

用排列组合来编码通信(七)——《我的5/4张牌的预言》

早点关注我&#xff0c;精彩不错过&#xff01; 从《5张牌的预言》开始&#xff0c;前面介绍了3个拓展思路&#xff0c;分别从引入额外信息解放选牌&#xff08;Eigens value&#xff09;&#xff0c;引入正反信息来编码&#xff08;ups and downs&#xff09;&#xff0c;继续…

从零开始学架构——可扩展架构模式

可扩展架构模式的基本思想和模式 软件系统与硬件和建筑系统最大的差异在于软件是可扩展的&#xff0c;一个硬件生产出来后就不会再进行改变、一个建筑完工后也不会再改变其整体结构 例如&#xff0c;一颗 CPU 生产出来后装到一台 PC 机上&#xff0c;不会再返回工厂进行加工以…

从零玩转前后端加解密之SM2-sm2

title: 从零玩转前后端加解密之SM2 date: 2022-08-21 19:42:00.907 updated: 2023-03-30 13:28:48.866 url: https://www.yby6.com/archives/sm2 categories: - 加密算法 - 从零玩转系列 tags: - 加密算法 - sm2 前言 SM2是国家密码管理局于2010年12月17日发布的椭圆曲线公钥…

工业互联网UWB定位系统源码,支持自定义开发

工厂人员定位系统&#xff0c;采用UWB定位技术&#xff0c;通过在厂区内部署一定数量的定位基站&#xff0c;以及为人员、车辆、物资佩戴标签卡的形式&#xff0c;实时获取人员精确位置&#xff0c;精度高达10cm。 文末获取联系 工厂人员定位系统可实现物资/车辆实时定位&#…

不同厂家对讲机耳塞耳挂/领夹型988对讲机如何写频改频点/频率能互相通信

988型号都是很多厂家代工出来的,代工出来默认的频点都不一样,有可能买回来的2个不同厂家生产的对讲机,这样它们要能通讯,必须要同频点才能互通,它一般出厂设定16个频道,长按+和-键来切换频道。 需要用到typeC 的写频线,其实是用CH430芯片的usb写频线,可以找厂家要写频线…

编程语言中,循环变量通常都用 i?你知道为什么吗?

01 前天&#xff0c;我在朋友圈发了一个问题&#xff1a; 为什么编程中&#xff0c;循环变量通常都是用 i ? 没想到&#xff0c;回复的人这么多&#xff01;要连翻好几页。 这个问题&#xff0c;有 2/3 的人回答正确&#xff0c;有少部分人知道&#xff0c;但是不太确定。 习惯…

Hadoop基础学习---2、Hadoop概述

1、Hadoop概述 1.1 Hadoop是什么&#xff1f; 1、Hadoop是一个又Apache基金会所开发的分布式系统基础架构。 2、主要解决海量数据的存储和海量数据的分析计算。 3、广义上来说&#xff0c;Hadoop通常是指一个更广泛的概念——Hadoop生态圈。 1.2 Hadoop 优势&#xff08;4高…

六级备考28天|CET-6|翻译井冈山|2021年12月|8:20~9:40+ ~10:17

目录 四级翻译5篇必练真题 六级翻译5篇必练真题 井冈山 四级翻译5篇必练真题 ①2023年3月一卷①自驾游 ②2022年 12月一卷③立秋 ③2022年6月一卷①拔苗助长 ④2021年 12月一卷②大运河 ⑤2021年6月一卷③普洱茶 六级翻译5篇必练真题 ①2023年3月一卷②郑和下西洋 ②2022年…

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

可能有些人会觉得这篇似曾相识&#xff0c;没错&#xff0c;这篇是由原文章进行二次开发的。 前阵子有些事情&#xff0c;但最近看到评论区说原文章最后实现的是单模块的验证&#xff0c;由于过去太久也懒得验证&#xff0c;所以重新写了一个完整的可以跑得动的一个。 OK&#…

nvidia-smi 失效解决

服务器重启后&#xff0c;跑模型发现&#xff1a; RuntimeError: Attempting to deserialize object on a CUDA device but torch.cuda.is_available() is False. If you are running on a CPU-only machine, please use torch.load with map_locationtorch.device(cpu) to ma…

Linux常用命令——hping3命令

在线Linux命令查询工具 hping3 测试网络及主机的安全 补充说明 hping是用于生成和解析TCPIP协议数据包的开源工具。创作者是Salvatore Sanfilippo。目前最新版是hping3&#xff0c;支持使用tcl脚本自动化地调用其API。hping是安全审计、防火墙测试等工作的标配工具。hping优…

【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读

项目地址 项目地址 https://toscode.gitee.com/nepxion/Aquarius 项目介绍 Nepxion Aquarius是一款基于Redis Zookeeper的分布式应用组件集合&#xff0c;包含分布式锁&#xff0c;缓存&#xff0c;ID生成器&#xff0c;限速限流器。它采用Nepxion Matrix AOP框架进行切面架构…