您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
SpringBoot内置tomcat启动过程及原理
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
SpringBoot内置tomcat启动过程及原理
自猿其说Tech
2022-08-16
IP归属:未知
1419浏览
计算机编程
### 1 背景 SpringBoot是一个框架,一种全新的编程规范,他的产生简化了框架的使用,同时也提供了很多便捷的功能,比如内置tomcat就是其中一项,他让我们省去了搭建tomcat容器,生成war,部署,启动tomcat。因为内置了启动容器,应用程序可以直接通过 Maven 命令将项目编译成可执行的 jar 包,通过 java -jar 命令直接启动,不需要再像以前一样,打包成 War 包,然后部署在 Tomcat 中。那么内置tomcat是如何实现的呢 ### 2 tomcat启动过程及原理 #### 2.1 下载一个springboot项目 在这里下载一个项目https://start.spring.io/ 也可以在idea新建SpringBoot-Web工程. ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` 点击 pom.xml会有 tomcat依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.1.2.RELEASE</version> <scope>compile</scope> </dependency> ``` #### 2.2 从启动入口开始一步步探索 ![](//img1.jcloudcs.com/developer.jdcloud.com/d86d3e9e-1fdd-4b7e-960d-af8b042cf73520220816141340.png) 点击进入run方法 ```java public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } //继续点击进入run方法 public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); } ``` 进入到这个run方法之后就可以看到,我们认识的一些初始化事件。主要的过程也是在这里完成的。 ![](//img1.jcloudcs.com/developer.jdcloud.com/8c5f91cb-9343-4ebf-aab5-af370e541e9620220816141405.png) #### 2.3 源码代码流程大致是这样 ```java /** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); /**1、配置系统属性*/ configureHeadlessProperty(); /**2.获取监听器*/ SpringApplicationRunListeners listeners = getRunListeners(args); /**发布应用开始启动事件 */ listeners.starting(); try { /** 3.初始化参数 */ ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); /** 4.配置环境*/ ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); /**5.创建应用上下文*/ context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); /**6.预处理上下文*/ prepareContext(context, environment, listeners, applicationArguments, printedBanner); /**6.刷新上下文*/ refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } /** 8.发布应用已经启动事件 */ listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { /** 9.发布应用已经启动完成的监听事件 */ listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; } ``` ![](//img1.jcloudcs.com/developer.jdcloud.com/37adc850-6809-49d6-af86-233efb50c5f020220816141437.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/b12ad8b5-6275-422f-a439-d5b0c6a32f5b20220816141443.png) 代码中主要就是通过 switch 语句,根据 webApplicationType 的类型来创建不同的 ApplicationContext: - DEFAULT_SERVLET_WEB_CONTEXT_CLASS:Web类型,实例化 AnnotationConfigServletWebServerApplicationContext - DEFAULT_REACTIVE_WEB_CONTEXT_CLASS:响应式Web类型,实例化 AnnotationConfigReactiveWebServerApplicationContext - DEFAULT_CONTEXT_CLASS:非Web类型,实例化 AnnotationConfigApplicationContext #### 2.4 创建完应用上下文之后,我们在看刷新上下文方法 ![](//img1.jcloudcs.com/developer.jdcloud.com/0d3ea573-abdb-4ef7-999b-d7e3b01e6f4220220816141510.png) 一步步通过断点点击方法进去查看,我们看到很熟悉代码spring的相关代码。 ![](//img1.jcloudcs.com/developer.jdcloud.com/c433bb31-e90c-4b83-a82d-4d4dab4acd6e20220816141521.png) ```java @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. //初始化前的准备工作,主要是一些系统属性、环境变量的校验,比如Spring启动需要某些环境变量,可以在这个地方进行设置和校验 prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. //准备bean工厂 注册了部分类 prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. //注册bean工厂后置处理器,并解析java代码配置bean定义 invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. //注册bean后置处理器,并不会执行后置处理器,在后面实例化的时候执行 registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. //初始化事件监听多路广播器 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. //待子类实现,springBoot在这里实现创建内置的tomact容器 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(); } } } ``` #### 2.5 onRefresh() 方法是调用其子类实现的 也就是 ServletWebServerApplicationContext ![](//img1.jcloudcs.com/developer.jdcloud.com/8402c82d-4c48-48a5-a2fd-d0cc764f103420220816141605.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/ffed19b3-9ad8-4c2c-8d35-8e6f1feb201220220816141614.png) ``` /** 得到Servlet工厂 **/ this.webServer = factory.getWebServer(getSelfInitializer()); ``` 其中 createWebServer() 方法是用来启动web服务的,但是还没有真正启动 Tomcat,只是通过ServletWebServerFactory 创建了一个 WebServer,继续来看这个 ServletWebServerFactory: this.webServer = factory.getWebServer(getSelfInitializer()); 这个方法可以看出TomcatServletWebServerFactory的实现。相关Tomcat的实现。 #### 2.6 TomcatServletWebServerFactory 的 getWebServer() 方法 清晰的看到new 出来了一个Tomcat. ![](//img1.jcloudcs.com/developer.jdcloud.com/cd45e43d-cdc2-4a4e-89dc-da2e0a325b5020220816141653.png) #### 2.7 Tomcat创建之后,继续分析Tomcat的相关设置和参数 ```java @Override public WebServer getWebServer(ServletContextInitializer... initializers) { /** 1、创建Tomcat实例 **/ Tomcat tomcat = new Tomcat(); //创建Tomcat工作目录 File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); /** 2、给创建好的tomcat设置连接器connector **/ tomcat.setConnector(connector); /** 3.设置不自动部署 **/ tomcat.getHost().setAutoDeploy(false); /** 4.配置Tomcat容器引擎 **/ configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } /**准备Tomcat的StandardContext,并添加到Tomcat中*/ prepareContext(tomcat.getHost(), initializers); /** 将创建好的Tomcat包装成WebServer返回**/ return getTomcatWebServer(tomcat); } ``` #### 2.8 继续点击getTomcatWebServer方法,找到initialize()方法,可以看到tomcat.start();启动tomcat服务方法。 ![](//img1.jcloudcs.com/developer.jdcloud.com/0471f02c-5424-49c5-a6cf-b6bfe999ff2c20220816141811.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/8c15baa3-85d8-44d6-9434-ae00942509f820220816141823.png) ```java // Start the server to trigger initialization listeners //启动tomcat服务 this.tomcat.start(); //开启阻塞非守护进程 startDaemonAwaitThread(); ``` //Tomcat.java ![](//img1.jcloudcs.com/developer.jdcloud.com/3c09af42-01e3-430a-ab31-c69a8330a39320220816141839.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/91293a33-c5e0-4ba0-9049-3a8b127d1ffd20220816141846.png) #### 2.9 TomcatWebServer.java 控制台会打印这句话 Tomcat started on port(s): 8080 (http) with context path '' ![](//img1.jcloudcs.com/developer.jdcloud.com/85297e71-5058-46cf-83e5-2e602e2cb1b320220816141911.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/74585a7e-aa6f-4d27-b950-75770a33e97a20220816141918.png) ### 3 总结 SpringBoot的启动主要是通过实例化SpringApplication来启动的,启动过程主要做了如下几件事情: 配置系统属性、获取监听器,发布应用开始启动事件、初始化参数、配置环境、创建应用上下文、预处理上下文、刷新上下文、再次刷新上下文、发布应用已经启动事件、发布应用启动完成事件。而启动 Tomcat 是刷新上下文 这一步。 Spring Boot 创建 Tomcat 时,会先创建一个上下文,将 WebApplicationContext 传给 Tomcat; 启动 Web 容器,需要调用 getWebserver(),因为默认的 Web 环境就是 TomcatServletWebServerFactory,所以会创建 Tomcat 的 Webserver,这里会把根上下文作为参数给 TomcatServletWebServerFactory 的 getWebServer();启动 Tomcat,调用 Tomcat 中 Host、Engine 的启动方法。 #### 3.1 Tomcat相关名称介绍 ![](//img1.jcloudcs.com/developer.jdcloud.com/95a547b7-101a-4ee6-bb4d-64a3b24ec9c920220816141941.png) ------------ ###### 自猿其说Tech-JDL京东物流技术与数据智能部 ###### 作者:李岩科
原创文章,需联系作者,授权转载
上一篇:辅助测试和研发人员的一款小插件【数据安全】
下一篇:防腐层、门面模式及适配模式的本质
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2150243
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2150243
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号