SpringBoot中Tomcat是如何启动的

Easter79
• 阅读 490

SpringBoot中Tomcat是如何启动的

Spring Boot一个非常突出的优点就是不需要我们额外再部署Servlet容器,它内置了多种容器的支持。我们可以通过配置来指定我们需要的容器。

本文以我们平时最常使用的容器Tomcat为列来介绍以下两个知识点:

  • Spring Boot是怎么整合启动Tomcat容器的;

  • 在Spring Boot中,怎么进行Tomcat的深度配置。

Spring Boot整合启动Tomcat的流程

对于看源代码,每个人都有自己的方法。我自己在看源代码的时候喜欢结合IDEA的Debug功能一起看。比如说现在我们要研究Spring Boot是在哪个环节点启动Tomcat的。

我的思路是:Tomcat在启动时会调用各个组件的init方法和start方法,那么我只需要在这些方法上打上端点,然后就能在调用栈上看出Spring Boot是在哪个环节点启用 Tomcat的了。

按照这个思路,我在Tomcat的Connector组件的init方法上打了端点,通过调用栈能很清楚的看出Spring Boot是在容器的onRefresh方法中调用Tomcat的。

SpringBoot中Tomcat是如何启动的

找到了调用点,那么一切都好办了。从上面的方法中可以看出,重点内容就在this.createWebServer()这个方法中。

在Spring Boot中使用的容器类是ServletWebServerApplicationContext系列的容器,这个系列的容器可以内嵌Web容器。这个我们可以从这个容器的属性和方法中可以看出来。

public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext {     //...省略部分代码     public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";     //内嵌容器     private volatile WebServer webServer;     private ServletConfig servletConfig;         //...省略部分代码     //创建Web容器     private void createWebServer() {         WebServer webServer = this.webServer;         ServletContext servletContext = this.getServletContext();         //webServer和servletContext都是null,表示还没创建容器,进入创建容器的逻辑         if (webServer == null && servletContext == null) {             //获取创建容器的工厂,可以通过WebServerFactoryCustomizer接口对这个工厂进行自定义设置             ServletWebServerFactory factory = this.getWebServerFactory();             //具体的创建容器的方法,我们进去具体看下             this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});         } else if (servletContext != null) {             try {                 this.getSelfInitializer().onStartup(servletContext);             } catch (ServletException var4) {                 throw new ApplicationContextException("Cannot initialize servlet context", var4);             }         }         this.initPropertySources();     } }

下面是TomcatServletWebServerFactory的getWebServer方法。

`public class TomcatServletWebServerFactory的getWebServer{
//...

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    //创建Tomcat容器
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null ? this.baseDirectory
            : createTempDir("tomcat"));
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    //创建连接器,默认NIO模式,可以通过WebServerFactoryCustomizer改变具体模式
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    //自定义连接器
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    //可以通过WebServerFactoryCustomizer添加额外的连接器,这边将这些连接器绑定到Tomcat
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    //组测Listener、Filter和Servlet,自定义Context等操作
    //这个方法可以重点看下
    prepareContext(tomcat.getHost(), initializers);
    //创建TomcatWebServer,并调用start方法
    return getTomcatWebServer(tomcat);
}

//内嵌的Tomcat容器
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
  Assert.notNull(tomcat, "Tomcat Server must not be null");
  this.tomcat = tomcat;
  this.autoStart = autoStart;
        //这边触发Tomcat的启动流程,是Tomcat启动的入口点
  initialize();
}
//...省略部分代码
}
`

代码执行顺序如下:SpringBoot中Tomcat是如何启动的 至此Spring Boot内嵌的Tomcat已将顺序启动了。那么Spring Boot是在什么时候注册DispatchServlet的呢?

Spring Boot注册DispatcherServlet

在传统的Spring MVC项目中,我们都会在web.xml中注册DispatcherServlet这个入口类,那么在Spring Boot中是在哪里注册的呢?

大家如果看Spring Boot的源代码,这边有个小技巧大家可以参考下。就是Spring Boot把之前传统项目中的配置项都通过AutoConfig的形式做配置了。所以这边在寻找DispatcherServlet是在哪里配置的也可以顺着这个思路去寻找。

在IDEA的类查找功能中输入DispatcherServlet关键字,我们能看到一个DispatcherServletAutoConfiguration类。从名字上就能看出这个类是DispatcherServlet的自动配置类,我们点进去看下是否是在这个类内部注册的DispatcherServlet?

`@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {

 /*
  * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
  */
 public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

 /*
  * The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
  */
 public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

 @Configuration
 @Conditional(DefaultDispatcherServletCondition.class)
 @ConditionalOnClass(ServletRegistration.class)
 @EnableConfigurationProperties(WebMvcProperties.class)
 protected static class DispatcherServletConfiguration {

  private final WebMvcProperties webMvcProperties;

  public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
   this.webMvcProperties = webMvcProperties;
  }

  @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
  public DispatcherServlet dispatcherServlet() {
   DispatcherServlet dispatcherServlet = new DispatcherServlet();
   dispatcherServlet.setDispatchOptionsRequest(
     this.webMvcProperties.isDispatchOptionsRequest());
   dispatcherServlet.setDispatchTraceRequest(
     this.webMvcProperties.isDispatchTraceRequest());
   dispatcherServlet.setThrowExceptionIfNoHandlerFound(
     this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
   return dispatcherServlet;
  }

  @Bean
  @ConditionalOnBean(MultipartResolver.class)
  @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
  public MultipartResolver multipartResolver(MultipartResolver resolver) {
   // Detect if the user has created a MultipartResolver but named it incorrectly
   return resolver;
  }

 }

 @Configuration
 @Conditional(DispatcherServletRegistrationCondition.class)
 @ConditionalOnClass(ServletRegistration.class)
 @EnableConfigurationProperties(WebMvcProperties.class)
 @Import(DispatcherServletConfiguration.class)
 protected static class DispatcherServletRegistrationConfiguration {

  private final ServerProperties serverProperties;

  private final WebMvcProperties webMvcProperties;

  private final MultipartConfigElement multipartConfig;

  public DispatcherServletRegistrationConfiguration(
    ServerProperties serverProperties, WebMvcProperties webMvcProperties,
    ObjectProvider multipartConfigProvider) {
   this.serverProperties = serverProperties;
   this.webMvcProperties = webMvcProperties;
   this.multipartConfig = multipartConfigProvider.getIfAvailable();
  }

        //很熟悉的代码有没有,ServletRegistrationBean就是我们上一节中介绍的注册Servlet的方式
        //只不过这边注册的是DispatcherServlet这个特殊的Servlet
  @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
  @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
  public ServletRegistrationBean dispatcherServletRegistration(
    DispatcherServlet dispatcherServlet) {
   ServletRegistrationBean registration = new ServletRegistrationBean<>(
     dispatcherServlet,
     this.serverProperties.getServlet().getServletMapping());
   registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
   registration.setLoadOnStartup(
     this.webMvcProperties.getServlet().getLoadOnStartup());
   if (this.multipartConfig != null) {
    registration.setMultipartConfig(this.multipartConfig);
   }
   return registration;
  }

 }

//...省略部分代码
}
`

好了通过这边的介绍,我们知道DispatcherServlet是通过DispatcherServletAutoConfiguration这个自动配置类注册的。

SpringBoot中Tomcat是如何启动的

这边给出一个配置的列子

server: port: ${port:9999} tomcat: accept-count: 200     #最好进行这段配置,默认会在tmp目录下创建,Linux有时会有定时任务删除tmp目录下的内容 basedir: my-tomcat accesslog: enabled: true pattern: '%t %a "%r" %s %S (%b M) (%D ms)' max-http-post-size: 2MB max-swallow-size: 2M uri-encoding: GBK threads: max: 100 min-spare: 10

具体使用时可以参考Spring Boo官网关于Tomcat的配置。

一些其他类

Spring Boot还提供了很多自定义类,让用户对Tomcat的组件做自定义配置。这个符合Spring的设计哲学:只提供选择,而不是强制用户使用某项技术。

关于Tomcat的自定义配置类还有以下几个,大家可以按需使用。

  • WebServerFactoryCustomizer接口:自定义Web容器工厂

  • WebServerFactoryCustomizerBeanPostProcessor处理类: WebServerFactoryCustomizer类通过 WebServerFactoryCustomizerBeanPostProcessor类生效

  • TomcatConnectorCustomizer:连接器自定义处理类

  • TomcatContextCustomizer:Context自定义接口

作者 | 程序员自由之路
来源 | http://suo.im/5xzLTt

如果本文对你有帮助,

别忘记给我个三连:

点赞,转发,评论

咱们下期见!

收藏 等于白嫖点赞 才是真情!

End

干货分享

这里为大家准备了一份小小的礼物,关注公众号,输入如下代码,即可获得百度网盘地址,无套路领取!

001:《程序员必读书籍》
002:《从无到有搭建中小型互联网公司后台服务架构与运维架构》
003:《互联网企业高并发解决方案》
004:《互联网架构教学视频》
006:《SpringBoot实现点餐系统》
007:《SpringSecurity实战视频》
008:《Hadoop实战教学视频》
009:《腾讯2019Techo开发者大会PPT》

010: 微信交流群

近期热文top

1、关于JWT Token 自动续期的解决方案

2、还不了解ETL,看看这篇文章?

3、架构师之路-服务器硬件扫盲

4、架构师之路-微服务技术选型

5、RocketMQ进阶 - 事务消息

SpringBoot中Tomcat是如何启动的

我就知道你“在看”

SpringBoot中Tomcat是如何启动的

本文分享自微信公众号 - JAVA日知录(javadaily)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
SpringBoot中Tomcat是如何启动的
!(https://oscimg.oschina.net/oscnet/0635520b1cc748869d16a7204dc88786.png)SpringBoot一个非常突出的优点就是不需要我们额外再部署Servlet容器,它内置了多种容器的支持。我们可以通过配置来指定我们需要的容器。本文以我们平时最常使用的容器Tomcat为列来介
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k