Spring Boot内嵌Tomcat原理

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第29天,点击查看活动详情

本次的spring-boot-starter-parent版本为2.3.0。

之前分析了Spring boot的启动源码的大概流程,结尾我们说了一些内嵌tomcat原理的原理,现在我们来深入探究一下!

1 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的参数和配置。

在这里插入图片描述

2 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服务器就是在此启动的!

  1. /**
  2. * ServletWebServerApplicationContext实现的方法
  3. */
  4. @Override
  5. protected void onRefresh(){
  6. //调用父类的逻辑
  7. super.onRefresh();
  8. try{
  9. /*
  10. * 关键方法,创建webserver
  11. */
  12. createWebServer();
  13. }
  14. catch( Throwable ex ){
  15. throw new ApplicationContextException( "Unable to start web server", ex );
  16. }
  17. }

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

2.1 createWebServer创建web服务

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

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

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

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

  1. private volatile WebServer webServer;
  2. /**
  3. * ServletWebServerApplicationContext的方法
  4. * <p>
  5. * 创建web服务
  6. */
  7. private void createWebServer(){
  8. //获取WebServer,这里默认是空的
  9. WebServer webServer = this.webServer;
  10. //获取ServletContext,即servlet上下文,这里默认是空的
  11. ServletContext servletContext = getServletContext();
  12. /*
  13. * 获取webServer,初始化web服务
  14. */
  15. if( webServer == null && servletContext == null ){
  16. //获取web服务工厂,默认就是TomcatServletWebServerFactory
  17. ServletWebServerFactory factory = getWebServerFactory();
  18. /*
  19. * 通过web服务工厂获取web服务,核心代码
  20. * 创建内嵌的Tomcat并启动
  21. */
  22. this.webServer = factory.getWebServer( getSelfInitializer() );
  23. /*
  24. * 注册WebServerGracefulShutdownLifecycle的实例到容器中
  25. * ReactiveWebServerApplicationContext容器启动完毕后会调用该实例的start方法
  26. * ReactiveWebServerApplicationContext容器销毁时将会调用该实例的stop方法
  27. */
  28. getBeanFactory().registerSingleton( "webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle( this.webServer ) );
  29. /*
  30. * 注册WebServerStartStopLifecycle的实例到容器中
  31. * ServletWebServerApplicationContext容器启动完毕后会调用该实例的start方法尝试启动webServer并发送事件
  32. * ServletWebServerApplicationContext容器销毁时将会调用该实例的stop方法销毁webServer
  33. */
  34. getBeanFactory().registerSingleton( "webServerStartStop", new WebServerStartStopLifecycle( this, this.webServer ) );
  35. }
  36. else if( servletContext != null ){
  37. try{
  38. getSelfInitializer().onStartup( servletContext );
  39. }
  40. catch( ServletException ex ){
  41. throw new ApplicationContextException( "Cannot initialize servlet context", ex );
  42. }
  43. }
  44. //初始化ConfigurableWebEnvironment类型的配属数据源
  45. initPropertySources();
  46. }

2.1.1 getWebServerFactory获取web服务工厂

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

  1. /**
  2. * ServletWebServerApplicationContext的方法
  3. * <p>
  4. * 获取ServletWebServerFactory,用于初始化webServer
  5. * 默认返回TomcatServletWebServerFactory
  6. */
  7. protected ServletWebServerFactory getWebServerFactory(){
  8. //从容器中搜索ServletWebServerFactory类型的beanName数组
  9. //之前的ServletWebServerFactoryConfiguration配置类就会像容器中
  10. //注入ServletWebServerFactory的bean,默认就是TomcatServletWebServerFactory
  11. String[] beanNames = getBeanFactory().getBeanNamesForType( ServletWebServerFactory.class );
  12. //没有web服务工厂
  13. if( beanNames.length == 0 ){
  14. throw new ApplicationContextException(
  15. "Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean." );
  16. }
  17. //有多个web服务工厂
  18. if( beanNames.length > 1 ){
  19. throw new ApplicationContextException(
  20. "Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(
  21. beanNames ) );
  22. }
  23. //从容器中获取web服务工厂的实例
  24. return getBeanFactory().getBean( beanNames[ 0 ], ServletWebServerFactory.class );
  25. }

2.1.2 getWebServer获取web服务

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

该方法中的一些名词比如baseDir、connector、Service、Host、AutoDeploy 、Engine等等都是Tomcat中的概念,我们之前就介绍过了,在此不再赘述:Tomcat的核心组件以及server.xml配置全解【一万字】

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

  1. /**
  2. * TomcatServletWebServerFactory的方法
  3. * 创建内嵌的Tomcat
  4. *
  5. * @param initializers 初始化器
  6. * @return Tomcat的web服务
  7. */
  8. @Override
  9. public WebServer getWebServer( ServletContextInitializer... initializers ){
  10. if( this.disableMBeanRegistry ){
  11. Registry.disableRegistry();
  12. }
  13. //创建Tomcat实例
  14. Tomcat tomcat = new Tomcat();
  15. //设置Tomcat的基本目录
  16. File baseDir = ( this.baseDirectory != null ) ? this.baseDirectory : createTempDir( "tomcat" );
  17. tomcat.setBaseDir( baseDir.getAbsolutePath() );
  18. //设置Connector,用于接受请求发挥响应
  19. Connector connector = new Connector( this.protocol );
  20. connector.setThrowOnFailure( true );
  21. tomcat.getService().addConnector( connector );
  22. //自定义连接器
  23. customizeConnector( connector );
  24. tomcat.setConnector( connector );
  25. //是否自动部署
  26. tomcat.getHost().setAutoDeploy( false );
  27. //设置Engine
  28. configureEngine( tomcat.getEngine() );
  29. //自己扩展的连接器
  30. for( Connector additionalConnector : this.additionalTomcatConnectors ){
  31. tomcat.getService().addConnector( additionalConnector );
  32. }
  33. //准备上下文
  34. prepareContext( tomcat.getHost(), initializers );
  35. //创建TomcatWebServer,启动Tomcat,返回TomcatWebServer
  36. return getTomcatWebServer( tomcat );
  37. }

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

相关文章:

  1. https://spring.io/
  2. Spring Framework 5.x 学习
  3. Spring MVC 5.x 学习
  4. Spring Framework 5.x 源码

文章标签:

原文连接:https://juejin.cn/post/7114679361042645022

相关推荐

云徙科技CTO李楠:技术和技术人,与数字化共同进化

Java通过反射注解赋值

Java开发学习(十五)----AOP入门案例及其工作流程解析

2022-07-21 第四组 java之继承

JavaScript进阶内容——BOM详解

注意 new BigDecimal(double val) 的使用

创建私有CA,我就用openSSL

JavaScript必须掌握的四大基础知识(数据类型、原型和原型链、闭包、异步 promise)

百度APP Android包体积优化实践(二)Dex行号优化

814. 二叉树剪枝 : 简单递归运用题

【综合笔试题】难度 3.5\u002F5,多解法热门二叉树笔试题

【java刷算法】牛客—剑指offer3栈、数组、递归、二分法的初步练习

JavaScript中一些流行的模块、vite快速搭建、vue3简梳理

JavaScript进阶内容——DOM详解

JavaScript基础系列(6):`this`这六种使用方式

【跟着大佬学JavaScript】之数组去重(结果对比)

Java开发学习(十四)----Spring整合Mybatis及Junit

Java代码优化的30个小技巧

JavaScript 中的单例内置对象(Global & Math)

JavaScript:聊聊作用域、作用域链与它的一些优化