写点什么

Spring Boot 是如何内嵌 Tomcat 的?原理剖析

  • 2023-03-03
    湖南
  • 本文字数:3848 字

    阅读完需:约 13 分钟

一、ServletWebServerFactoryConfiguration

实际上,Spring Boot 是指出多种服务器启动的,并不只是 Tomcat,还有 Jetty 等。因此我们可以猜测具体哪种服务器是可以配置的,而 Spring Boot 又是以自动配置闻名,那么这些服务器肯定与某些自动配置类相关。


实际上,Spring Boot 的 servlet web 服务器的配置类就是位于 spring-boot-autoconfigure.jar 下的/META-INF/spring.factories 文件中的一个名为 ServletWebServerFactoryAutoConfiguration 的自动配置类。


在该自动配置类中,会通过 @Import 想容器中注入四个配置类,我们可以看到,各种容器的 web 服务配置,Tomcat、Jetty、Undertow,其中 Tomcat 对应 EmbeddedTomcat。


这个 EmbeddedTomcat 配置类又会向 Spring 容器注入 TomcatServletWebServerFactory,这个类就是 Tomcat 启动的关键类,用于创建 TomcatWebServer。


另外,ServletWebServerFactoryAutoConfiguration 中还会注入一系列的 Customizer,用于修改内嵌 Tomcat 的参数和配置。

二、onRefresh 启动 web 服务

那么,这个 TomcatServletWebServerFactory 是怎么在什么时候被加载到容器中并使用的呢?Tomcat 又是什么时候被启动的呢?


之前的文章就讲过,在 Spring Boot 容器启动过程中,在创建容器之后,会执行刷新容器的操作,也就是 refresh()操作,这个操作实际上就是 Spring 容器的启动方法,将会加载 bean 以及各种配置。该方法是 Spring 项目的核心方法,源码非常多,我们在之前以及花了大量时间讲过了,在此不再赘述,之前的文章链接 Spring IoC 容器初始化源码。


在 refresh()方法中,有一个 onRefresh()方法。


这个 onRefresh 方法默认是一个空的实现,这是留给子类容器实现的扩展方法。这个方法是在所有的 bean 定义被注入到容器中之后调用的,而在 onRefresh 方法之后,则会对所有的普通单例 bean 进行实例化和初始化。


默认的 web 服务容器是 AnnotationConfigServletWebServerApplicationContext,它又继承了 ServletWebServerApplicationContext,该类就对 onRefresh 方法进行了实现,并且 Spring Boot 的 web 服务器就是在此启动的!

/** * ServletWebServerApplicationContext实现的方法 */@Overrideprotected void onRefresh(){   //调用父类的逻辑   super.onRefresh();   try{      /*       * 关键方法,创建webserver       */      createWebServer();   }   catch( Throwable ex ){      throw new ApplicationContextException( "Unable to start web server", ex );   }}
复制代码

可以看到,内部调用了 createWebServer 方法创建 web 服务器。

2.1 createWebServer 创建 web 服务

createWebServer 方法的代码如下,它会通过之前配置的 ServletWebServerFactory,获取 webServer,即创建 web 服务器。


一般我们使用的 ServletWebServerFactory 就是 TomcatServletWebServerFactory,使用的 webserver 就是 TomcatWebServer。


在创建了 webserver 之后,会想容器注入两个 SmartLifecycle 类型的 bean 实例,这实际上是一个扩展点的实例,用于实现容器回调。


其中,注册的 WebServerStartStopLifecycle 实例,在 ServletWebServerApplicationContext 类型的容器启动完毕后会调用该实例的 start 方法启动 webServer 并发送事件,在 ServletWebServerApplicationContext 类型的容器销毁时将会调用该实例的 stop 方法销毁 webServer。

private volatile WebServer webServer;
/** * ServletWebServerApplicationContext的方法 * <p> * 创建web服务 */private void createWebServer(){ //获取WebServer,这里默认是空的 WebServer webServer = this.webServer; //获取ServletContext,即servlet上下文,这里默认是空的 ServletContext servletContext = getServletContext(); /* * 获取webServer,初始化web服务 */ if( webServer == null && servletContext == null ){ //获取web服务工厂,默认就是TomcatServletWebServerFactory ServletWebServerFactory factory = getWebServerFactory(); /* * 通过web服务工厂获取web服务,核心代码 * 创建内嵌的Tomcat并启动 */ this.webServer = factory.getWebServer( getSelfInitializer() ); /* * 注册WebServerGracefulShutdownLifecycle的实例到容器中 * ReactiveWebServerApplicationContext容器启动完毕后会调用该实例的start方法 * ReactiveWebServerApplicationContext容器销毁时将会调用该实例的stop方法 */ getBeanFactory().registerSingleton( "webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle( this.webServer ) ); /* * 注册WebServerStartStopLifecycle的实例到容器中 * ServletWebServerApplicationContext容器启动完毕后会调用该实例的start方法尝试启动webServer并发送事件 * ServletWebServerApplicationContext容器销毁时将会调用该实例的stop方法销毁webServer */ getBeanFactory().registerSingleton( "webServerStartStop", new WebServerStartStopLifecycle( this, this.webServer ) ); } else if( servletContext != null ){ try{ getSelfInitializer().onStartup( servletContext ); } catch( ServletException ex ){ throw new ApplicationContextException( "Cannot initialize servlet context", ex ); } } //初始化ConfigurableWebEnvironment类型的配属数据源 initPropertySources();}
复制代码
2.1.1 getWebServerFactory 获取 web 服务工厂

该方法获取 web 服务工厂,工厂用于创建 web 服务。

/** * ServletWebServerApplicationContext的方法 * <p> * 获取ServletWebServerFactory,用于初始化webServer * 默认返回TomcatServletWebServerFactory */protected ServletWebServerFactory getWebServerFactory(){   //从容器中搜索ServletWebServerFactory类型的beanName数组   //之前的ServletWebServerFactoryConfiguration配置类就会像容器中   //注入ServletWebServerFactory的bean,默认就是TomcatServletWebServerFactory   String[] beanNames = getBeanFactory().getBeanNamesForType( ServletWebServerFactory.class );   //没有web服务工厂   if( beanNames.length == 0 ){      throw new ApplicationContextException(            "Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean." );   }   //有多个web服务工厂   if( beanNames.length > 1 ){      throw new ApplicationContextException(            "Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(                  beanNames ) );   }   //从容器中获取web服务工厂的实例   return getBeanFactory().getBean( beanNames[ 0 ], ServletWebServerFactory.class );}
复制代码
2.1.2 getWebServer 获取 web 服务

ServletWebServerFactory 的方法,用于获取 web 服务。其中 TomcatServletWebServerFactory 的方法用于创建 Tomcat 实例并返回 TomcatServer。


该方法中的一些名词比如 baseDir、connector、Service、Host、AutoDeploy 、Engine 等等都是 Tomcat 中的概念,我们之前就介绍过了,在此不再赘述了。


在最后的 getTomcatWebServer 方法中会对 Tomcat 服务器进行启动。控制台会输出日志:Tomcat initialized with port(s): 8080 (http)。

/** * TomcatServletWebServerFactory的方法 * 创建内嵌的Tomcat * * @param initializers 初始化器 * @return Tomcat的web服务 */@Overridepublic WebServer getWebServer( ServletContextInitializer... initializers ){
if( this.disableMBeanRegistry ){ Registry.disableRegistry(); } //创建Tomcat实例 Tomcat tomcat = new Tomcat(); //设置Tomcat的基本目录 File baseDir = ( this.baseDirectory != null ) ? this.baseDirectory : createTempDir( "tomcat" ); tomcat.setBaseDir( baseDir.getAbsolutePath() ); //设置Connector,用于接受请求发挥响应 Connector connector = new Connector( this.protocol ); connector.setThrowOnFailure( true ); tomcat.getService().addConnector( connector ); //自定义连接器 customizeConnector( connector ); tomcat.setConnector( connector ); //是否自动部署 tomcat.getHost().setAutoDeploy( false ); //设置Engine configureEngine( tomcat.getEngine() ); //自己扩展的连接器 for( Connector additionalConnector : this.additionalTomcatConnectors ){ tomcat.getService().addConnector( additionalConnector ); } //准备上下文 prepareContext( tomcat.getHost(), initializers ); //创建TomcatWebServer,启动Tomcat,返回TomcatWebServer return getTomcatWebServer( tomcat );}
复制代码

Tomcat 启动后的继续执行 Spring 的逻辑,初始化 bean 实例等等,Spring 容器初始化完毕之后,调用 WebServerStartStopLifecycle 的 start 方法,对 TomcatWebServer 进行启动,此时控制台会输出日志:“Tomcat started on port(s): 8080 (http) with context path”。


作者:刘 Java

链接:https://juejin.cn/post/7114679361042645022

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
Spring Boot是如何内嵌Tomcat的?原理剖析_Java_做梦都在改BUG_InfoQ写作社区