【Java Web编程 十二】深入理解Tomcat系统架构及工作原理(下)

简介: 【Java Web编程 十二】深入理解Tomcat系统架构及工作原理(下)

Container组件

每个 Service 会包含一个容器。容器由一个引擎可以管理多个虚拟主机。每个虚拟主机可以管理多个 Web 应用。每个 Web 应用会有多个 Servlet 包装器。Engine、Host、Context 和 Wrapper,四个容器之间属于父子关系

四个组件的职责如下:

  • Engine:Servlet 的顶层容器,包含一个或多个Host 子容器
  • Host:虚拟主机,负责 web 应用的部署和 Context 的创建
  • Context:Web 应用上下文,包含多个 Wrapper,负责 web 配置的解析,管理所有的 Web 资源;
  • Wrapper:最底层的容器,是对 Servlet 的封装,负责 Servlet 实例的创建、执行和销毁。

容器的请求处理过程就是在 Engine、Host、Context 和 Wrapper 这四个容器之间层层调用,最后在 Servlet 中执行对应的业务逻辑。各容器都会有一个通道 Pipeline,每个通道上都会有一个 Basic Valve(如StandardEngineValve), 类似一个闸门用来处理 Request 和 Response 。其流程图如下:

请求路径查找流程

了解了两个核心组件后,我们把请求可以简单理解为:连接器的处理流程 + 容器的处理流程 = Tomcat 处理流程。Tomcat 是如何通过请求路径找到对应的虚拟站点?是如何找到对应的 Servlet 呢?我们再来回头看下配置文件里Service的部分:

<!-- Service-->
  <Service name="Catalina">
    <!-- Connector-->
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <!-- Container-->
    <Engine name="Catalina" defaultHost="localhost">
      <!-- Realm -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <!-- Host -->
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>

通过配置文件分析tomcat处理一个请求的流程,例如我们请求如下地址:http://localhost:8080/docs/list

  1. 连接器监听的端口是8080。由于请求的端口和监听的端口一致,connector接受了该请求,并将转换好的HttpServletRequest传递给容器
  2. 因为引擎的默认虚拟主机是 localhost,并且虚拟主机的目录是webapps。所以请求找到了 tomcat/webapps 目录。
  3. 解析的 docs是 web 程序的应用名,也就是context。此时请求继续从 webapps 目录下找 docs目录。有的时候我们也会把应用名省略。
  4. 解析的 api 是具体的业务逻辑地址。此时需要从 docs/WEB-INF/web.xml 或者通过注解找映射关系,最后调用具体的函数list进行操作。

以下是一个解析示例:

Tomcat生命周期

Tomcat 为了方便管理组件和容器的生命周期,定义了从创建、启动、到停止、销毁共 12 中状态,tomcat 生命周期管理了内部状态变化的规则控制,组件和容器只需实现相应的生命周期方法即可完成各生命周期内的操作(initInternal、startInternal、stopInternal、 destroyInternal)。

比如执行初始化操作时,会判断当前状态是否 New,如果不是则抛出生命周期异常;是则设置当前状态为 Initializing,并执行 initInternal 方法,由子类实现,方法执行成功则设置当 前状态为 Initialized,执行失败则设置为 Failed 状态。整体生命周期状态变化如下图所示:

Tomcat 的生命周期管理引入了事件机制,在组件或容器的生命周期状态发生变化时会通 知事件监听器,监听器通过判断事件的类型来进行相应的操作。事件监听器的添加可以在 server.xml 文件中进行配置,事实上我们从配置中确实可以看的出一些监听器相关配置:

<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

Tomcat 各类容器的配置过程就是通过添加 listener 的方式来进行的,从而达到配置逻辑与容器的解耦。

Tomcat流程管理

Tomcat有两个重要的流程处理,一个是Tomcat的启动流程,一个则是Tomcat的请求处理流程。

Tomcat启动流程

Tomcat的整体启动流程如下,通过如下的方式逐级推动子组件容器的状态变化:

  1. 启动从 Tomcat 提供的 start.sh 脚本开始,shell 脚本会调用 Bootstrap 的 main 方法,实际 调用了 Catalina 相应的 load、start 方法。
  2. load 方法会通过 Digester 进行 config/server.xml 的解析,在解析的过程中会根据 xml 中的关系 和配置信息来创建容器,并设置相关的属性。接着 Catalina 会调用 StandardServer 的 init 、start 方法进行容器的初始化和启动。
  3. StandardServer 完成 init 、 start 方法调用后,会一直监听来自 8005 端口(可配置),如果接收 到 shutdown 命令,则会退出循环监听,执行后续的 stop 和 destroy 方法,完成 Tomcat 容器的 关闭。同时也会调用 JVM 的 Runtime.getRuntime().addShutdownHook 方法,在虚拟机意外退 出的时候来关闭容器。

按照 xml 的配置关系,server 的子元素是 service,service 的子元素是顶层容器 Engine,每层容器有持有自己的子容器,而这些元素都实现了生命周期管理 的各个方法,因此就很容易的完成整个容器的启动、关闭等生命周期的管理。

所有容器都是继承自 ContainerBase,基类中封装了容器中的重复工作,负责启动容器相关的组 件 Loader、Logger、Manager、Cluster、Pipeline,启动子容器(线程池并发启动子容器,通过 线程池 submit 多个线程,调用后返回 Future 对象,线程内部启动子容器,接着调用 Future 对象 的 get 方法来等待执行结果)

Tomcat请求处理流程

上文中我们提到了Tomcat的路径查找方式,请求如何通过配置文件找到对应的处理逻辑,接下来我们完整的来看到一个请求的处理和响应的流程,用一个流程时序图表示如下:

假设来自客户的请求为:http://localhost:8080/Golden/loginServlet

  1. 请求被发送到本机端口 8080,被在那里侦听的 Coyote HTTP/1.1 Connector 获得
  2. Connector 把该请求交给它所在的 Service 的 Engine 来处理,并等待来自 Engine 的回应
  3. Engine 获得请求 localhost:8080/Golden/loginServlet, 匹配到名为 localhost 的 Host(即使匹配不到也把请求交给该 Host 处理,因为该 Host 被定义为该 Engine 的默认主机)
  4. Host 获得请求/Golden/loginServlet,Host 匹配到路径为 /Golden的 Context
  5. Context 获得请求 /loginServlet,匹配它所拥有的Wrapper
  6. Wrapper 构造 HttpServletRequest 对象和 HttpServletResponse 对象,作为参数调用 JspServlet 的 doGetdoPost 方法

返回流程:

  1. Context 把执行完了之后的 HttpServletResponse 对象返回给 Host
  2. Host 把 HttpServletResponse 对象返回给 Engine
  3. Engine 把 Response 对象返回给 Connector
  4. Connector 把 字节流数据返回给客户客户端浏览器。

这样就完成了一次整体的请求和响应。

Tomcat对JSP的处理

我们以上提到的整体处理流程其实是对Servlet的处理流程的概述,实际上大多数场景下我们请求的页面是JSP,而JSP需要被解析为Servlet来执行相关的业务逻辑。

Jasper引擎

客户端访问一个 jsp 文件,最终接收到的响应还是 静态代码,因此 jsp 可以看做是一个运行在服务端的脚本。而 Jasper 的作用就是对 jsp 语法进行解析,生成 servlet 并生成 class 字节码文件,最终将访问的结果直接响应客户端,上文中我们提到的web.xml配置文件中有如下片段,我们可以看到 web.xml 中配置了一个 JspServlet,用于处理所有的 .jsp 或者 .jspx 结尾的请求:

<servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
    </servlet>
    <!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- The mappings for the JSP servlet -->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

JSP文件解析流程

我们对JSP的请求在正式转为上述主流程时,前提还有个JSP的解析过程,当我们进行一个jsp请求时,在上述请求主流程的Context容器时,解析之后再继续执行上述主流程的:

最后一步的编译class文件流程详细如下:

总结一下

本篇Blog用较长的篇幅介绍了Tomcat的一些基本原理、组件,以及Tomcat如何处理Servlet请求以及如何解析JSP文件,理解了这些我想对Java Web的全局学习才真正的最终形成了闭环,以后的框架也无非是简化了这流程而已,学习起来应该会更加得心应手。

相关文章
|
8月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
767 1
|
8月前
|
存储 前端开发 Java
【JAVA】Java 项目实战之 Java Web 在线商城项目开发实战指南
本文介绍基于Java Web的在线商城技术方案与实现,涵盖三层架构设计、MySQL数据库建模及核心功能开发。通过Spring MVC + MyBatis + Thymeleaf实现商品展示、购物车等模块,提供完整代码示例,助力掌握Java Web项目实战技能。(238字)
1010 0
|
9月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
576 1
|
7月前
|
Cloud Native Serverless API
微服务架构实战指南:从单体应用到云原生的蜕变之路
🌟蒋星熠Jaxonic,代码为舟的星际旅人。深耕微服务架构,擅以DDD拆分服务、构建高可用通信与治理体系。分享从单体到云原生的实战经验,探索技术演进的无限可能。
微服务架构实战指南:从单体应用到云原生的蜕变之路
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
Cloud Native Devops 云计算
云计算的未来:云原生架构与微服务的革命####
【10月更文挑战第21天】 随着企业数字化转型的加速,云原生技术正迅速成为IT行业的新宠。本文深入探讨了云原生架构的核心理念、关键技术如容器化和微服务的优势,以及如何通过这些技术实现高效、灵活且可扩展的现代应用开发。我们将揭示云原生如何重塑软件开发流程,提升业务敏捷性,并探索其对企业IT架构的深远影响。 ####
469 3
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
10月前
|
缓存 Cloud Native Java
Java 面试微服务架构与云原生技术实操内容及核心考点梳理 Java 面试
本内容涵盖Java面试核心技术实操,包括微服务架构(Spring Cloud Alibaba)、响应式编程(WebFlux)、容器化(Docker+K8s)、函数式编程、多级缓存、分库分表、链路追踪(Skywalking)等大厂高频考点,助你系统提升面试能力。
1278 0
|
Java 开发者 微服务
从单体到微服务:如何借助 Spring Cloud 实现架构转型
**Spring Cloud** 是一套基于 Spring 框架的**微服务架构解决方案**,它提供了一系列的工具和组件,帮助开发者快速构建分布式系统,尤其是微服务架构。
2387 70
从单体到微服务:如何借助 Spring Cloud 实现架构转型

热门文章

最新文章