tomcat整体架构

Tomcat介绍

Tomcat是Apache Software Foundation(Apache软件基金会)开发的一款开源的Java Servlet

容器。它是一种Web服务器,用于在服务器端运行Java Servlet和JavaServer Pages (JSP)技术。它可

以为Java Web应用程序提供运行环境,并通过HTTP协议处理客户端请求。Tomcat也支持多种Web应

用程序开发技术,例如JavaServer Faces (JSF)、Java Persistence API (JPA)等。总的来说,Tomcat

是一款高效、稳定和易于使用的Web服务器。

Tomcat核心: Http服务器+Servlet容器
在这里插入图片描述

web应用部署的三种方式

1.拷贝到webapps目录下

//指定appBase  <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> 

2.server.xml 的Context标签下配置Context

<Context docBase="D:\mvc" path="/mvc" reloadable="true" /> 

path:指定访问该Web应用的URL入口(context-path)

docBase:指定Web应用的文件路径,可以给定绝对路径,也可以给定相对于的appBase属性的相对路径。

reloadable:如果这个属性设为true,tomcat服务器在运行状态下会监视在WEB-INF/classes和WEB-INF/lib目录下class文件的改动,如果监测到有class文件被更新的,服务器会自动重新加载Web应用。

3.在$CATALINA_BASE/conf/[enginename]/[hostname]/ 目录下(默认conf/Catalina/localhost)创建xml文

件,文件名就是contextPath。

比如创建mvc.xml,path就是/mvc

<Context docBase="D:\mvc" reloadable="true" />

Tomcat整体架构分析

Tomcat 要实现 2 个核心功能:

  1. 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。
  2. 加载和管理 Servlet,以及具体处理 Request 请求。

因此 Tomcat 设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。

连接器负责对外交流,容器负责内部处理。

Tomcat架构图

Tomcat的架构分为以下几个部分:

  1. Connector:Tomcat的连接器,用于接收请求并将其发送给容器。
  2. Container:Tomcat的容器,负责管理Servlet、JSP和静态资源的生命周期。
  3. Engine:Tomcat的引擎,管理容器的生命周期和分配请求。
  4. Host:Tomcat的主机,可以管理多个Web应用程序。
  5. Context:Tomcat的上下文,用于管理单个Web应用程序的配置信息。
  6. Servlet:Tomcat的Servlet,负责处理请求并生成响应。

总的来说,Tomcat的架构是一个分层的架构,每一层都有其自己的功能和职责。该架构可以提高

Tomcat的性能和可维护性,并使得Tomcat可以支持大量的Java Web应用程序。

在这里插入图片描述

Tomcat核心组件详解

Server 组件

指的就是整个 Tomcat 服务器,包含多组服务(Service),负责管理和启动各个Service,同时监听 8005 端口发过来的 shutdown 命令,用于关闭整个容器 。


Service组件

每个 Service 组件都包含了若干用于接收客户端消息的 Connector 组件和处理请求的 Engine 组件。 Service 组件还包含了若干 Executor 组件,每个 Executor 都是一个线程池,它可以为 Service内所有组件提供线程池执行任务。 Tomcat 内可能有多个 Service,这样的设计也是出于灵活性的考虑。通过在 Tomcat 中配置多个 Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。

在这里插入图片描述


连接器Connector组件

Tomcat 与外部世界的连接器,监听固定端口接收外部请求,传递给 Container,并将Container处理的结果返回给外部。连接器对 Servlet 容器屏蔽了不同的应用层协议及 I/O 模型,无论是 HTTP 还是 AJP,在容器中获取到的都是一个标准的 ServletRequest 对象。


容器Container组件

容器,顾名思义就是用来装载东西的器具,在 Tomcat 里,容器就是用来装载 Servlet 的。

Tomcat 通过一种分层的架构,使得 Servlet 容器具有很好的灵活性。Tomcat 设计了 4 种容器,分别是 Engine、Host、Context 和 Wrapper。这 4 种容器不是平行关系,而是父子关系。

  • Engine:引擎,Servlet 的顶层容器,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine;
  • Host:虚拟主机,负责 web 应用的部署和 Context 的创建。可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可以部署多个 Web 应用程序;
  • Context:Web 应用上下文,包含多个 Wrapper,负责 web 配置的解析、管理所有的 Web 资源。一个Context对应一个 Web 应用程序。
  • Wrapper:表示一个 Servlet,最底层的容器,是对 Servlet 的封装,负责 Servlet 实例的创建、执行和销毁。

在这里插入图片描述

请求定位 Servlet 的过程

Tomcat 是用 Mapper 组件来完成这个任务的。Mapper 组件的功能就是将用户请求的 URL 定位

到一个 Servlet,它的工作原理是:Mapper 组件里保存了 Web 应用的配置信息,其实就是容器组件

与访问路径的映射关系,比如 Host 容器里配置的域名、Context 容器里的 Web 应用路径,以及

Wrapper 容器里 Servlet 映射的路径,你可以想象这些配置信息就是一个多层次的 Map。当一个请求

到来时,Mapper 组件通过解析请求 URL 里的域名和路径,再到自己保存的 Map 里去查找,就能定

位到一个 Servlet。一个请求 URL 最后只会定位到一个 Wrapper 容器,也就是一个 Servlet。

在这里插入图片描述

Tomcat架构设计分析

Tomcat连接器需要实现的功能:

  • 监听网络端口。
  • 接受网络连接请求。
  • 读取请求网络字节流。
  • 根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的 Tomcat Request 对象。将 Tomcat Request 对象转成标准的 ServletRequest。
  • 调用 Servlet 容器,得到 ServletResponse。
  • 将 ServletResponse 转成 Tomcat Response 对象。
  • 将 Tomcat Response 转成网络字节流。
  • 将响应字节流写回给浏览器。

分析连接器详细功能列表,我们会发现连接器需要完成 3 个高内聚的功能:

  1. 网络通信。
  2. 应用层协议解析。
  3. Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化。

因此 Tomcat 的设计者设计了 3 个组件来实现这 3 个功能,分别是 EndPoint、Processor 和

Adapter。

  1. EndPoint 负责提供字节流给 Processor;
  2. Processor 负责提供 Tomcat Request 对象给 Adapter;
  3. Adapter 负责提供 ServletRequest 对象给容器。

由于 I/O 模型和应用层协议可以自由组合,比如 NIO + HTTP 或者 NIO2 + AJP。Tomcat 的设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫 ProtocolHandler 的接口来封装这两种变化点。

连接器

ProtocolHandler

连接器用 ProtocolHandler 来处理网络连接和应用层协议,包含了 2 个重要部件:EndPoint 和 Processor。

在这里插入图片描述

连接器用 ProtocolHandler 接口来封装通信协议和 I/O 模型的差异,ProtocolHandler 内部又分为 EndPoint 和 Processor 模块,EndPoint 负责底层 Socket 通信,Proccesor 负责应用层协议解析。连接器通过适配器 Adapter 调用容器。


EndPoint

EndPoint 是通信端点,即通信监听的接口,是具体的 Socket 接收和发送处理器,是对传输层的抽象,因此 EndPoint 是用来实现 TCP/IP 协议的。

EndPoint 是一个接口,对应的抽象实现类是 AbstractEndpoint,而 AbstractEndpoint 的具体子类,比如在 NioEndpoint 和 Nio2Endpoint 中,有两个重要的子组件:Acceptor 和 SocketProcessor。其中 Acceptor 用于监听 Socket 连接请求。SocketProcessor 用于处理接收到的 Socket 请求,它实现 Runnable 接口,在 Run 方法里调用协议处理组件 Processor 进行处理。为了提高处理能力,SocketProcessor 被提交到线程池来执行,而这个线程池叫作执行器(Executor)。


Processor

Processor 用来实现 HTTP/AJP 协议,Processor 接收来自 EndPoint 的 Socket,读取字节流解析成 Tomcat Request 和 Response 对象,并通过 Adapter 将其提交到容器处理,Processor 是对应用层协议的抽象。

Processor 是一个接口,定义了请求的处理等方法。它的抽象实现类 AbstractProcessor 对一些协议共有的属性进行封装,没有对方法进行实现。具体的实现有 AJPProcessor、HTTP11Processor等,这些具体实现类实现了特定协议的解析方法和请求处理方式。


Adapter

由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat 定义了自己的 Request 类来“存放”这些请求信息。ProtocolHandler 接口负责解析请求并生成 Tomcat Request 类。但是这个 Request 对象不是标准的 ServletRequest,也就意味着,不能用 Tomcat Request 作为参数来调用容 器。Tomcat 设计者的解决方案是引入 CoyoteAdapter,这是适配器模式的经典运用,连接器调用CoyoteAdapter 的 Sevice 方法,传入的是 Tomcat Request 对象,CoyoteAdapter 负责将 Tomcat Request 转成 ServletRequest,再调用容器的 Service 方法。

Pipeline-Valve 责任链模式设计

连接器中的 Adapter 会调用容器的 Service 方法来执行 Servlet,最先拿到请求的是Engine 容器,Engine 容器对请求做一些处理后,会把请求传给自己子容器 Host 继续处理,依次类推,最后这个请求会传给 Wrapper 容器,Wrapper 会调用最终的 Servlet 来处理。


Vavle接口设计
理解它的设计,第一步就是阀门设计。Valve 表示一个处理点,比如权限认证和记录日志。

public interface Valve { 
public Valve getNext(); 
public void setNext(Valve valve); 
public void invoke(Request request, Response response) throws IOException,ServletException;
}

Pipline接口设计
由于Pipline是为容器设计的,所以它在设计时加入了一个Contained接口, 就是为了制定当前Pipline所属的容器

public interface Pipeline extends Contained {// 基础的处理阀
public Valve getBasic();
public void setBasic(Valve valve);// 对节点(阀门)增删查
public void addValve(Valve valve);
public Valve[] getValves();
public void removeValve(Valve valve);// 获取第一个节点,遍历的起点,所以需要有这方法
public Valve getFirst();// 是否所有节点(阀门)都支持处理Servlet3异步处理
public boolean isAsyncSupported();// 找到所有不支持Servlet3异步处理的阀门
public void findNonAsyncValves(Set<String> result);
}

Pipeline 中维护了 Valve 链表,Valve 可以插入到 Pipeline 中,对请求做某些处理。整个调用链的触发是 Valve 来完成的,Valve 完成自己的处理后,调用 getNext.invoke() 来触发下一个 Valve 调用。每一个容器都有一个 Pipeline 对象,只要触发这个 Pipeline 的第一个 Valve,这个容器里Pipeline 中的 Valve 就都会被调用到。Basic Valve 处于 Valve 链表的末端,它是 Pipeline 中必不可少的一个 Valve,负责调用下层容器的 Pipeline 里的第一个 Valve。
在这里插入图片描述
Valve 和 Filter 的区别:

  • Valve 是 Tomcat 的私有机制,与 Tomcat 的基础架构 /API 是紧耦合的。Servlet API 是公有的标准,所有的Web 容器包括 Jetty 都支持 Filter 机制。
  • Valve 工作在 Web 容器级别,拦截所有应用的请求;而 Servlet Filter 工作在应用级别,只能拦截某个 Web 应 用的所有请求。

Tomcat生命周期设计

如果想让Tomcat能够对外提供服务,我们需要创建、组装并启动Tomcat组件;在服务停止的时候,我们还需要释放资源,销毁Tomcat组件,这是一个动态的过程。Tomcat 需要动态地管理这些组件的生命周期。


一键式启停:LifeCycle 接口
系统设计就是要找到系统的变化点和不变点。这里的不变点就是每个组件都要经历创建、初始化、启动这几个过程,这些状态以及状态的转化是不变的。而变化点是每个具体组件的初始化方法,也就是启动方法是不一样的。因此,我们把不变点抽象出来成为一个接口,这个接口跟生命周期有关,叫作 LifeCycle。LifeCycle 接口里应该定义这么几个方法:init()、start()、stop() 和 destroy(),每个具体的组件去实现这些方法。

public interface Lifecycle {
/** 第1类:针对监听器 **/
// 添加监听器
public void addLifecycleListener(LifecycleListener listener);
// 获取所有监听器
public LifecycleListener[] findLifecycleListeners();
// 移除某个监听器public void removeLifecycleListener(LifecycleListener listener);/** 第2类:针对控制流程 **/
// 初始化方法
public void init() throws LifecycleException;
// 启动方法
public void start() throws LifecycleException;
// 停止方法,和start对应
public void stop() throws LifecycleException;
// 销毁方法,和init对应
public void destroy() throws LifecycleException;/** 第3类:针对状态 **/
// 获取生命周期状态
public LifecycleState getState();
// 获取字符串类型的生命周期状态
public String getStateName();
}

在父组件的 init() 方法里需要创建子组件并调用子组件的 init() 方法。同样,在父组件的 start() 方法里也需要调用子组件的 start() 方法,因此调用者可以无差别的调用各组件的 init() 方法和 start() 方法,这就是组合模式的使用,并且只要调用最顶层组件,也就是 Server 组件的 init() 和 start() 方法,整个 Tomcat 就被启动起来了。


可扩展性:LifeCycle 事件
因为各个组件 init() 和 start() 方法的具体实现是复杂多变的,比如在 Host 容器的启动方法里需要扫描 webapps 目录下的 Web 应用,创建相应的 Context 容器,如果将来需要增加新的逻辑,直接修改 start() 方法?这样会违反开闭原则,那如何解决这个问题呢?开闭原则说的是为了扩展系统的功能,你不能直接修改系统中已有的类,但是你可以定义新的类。组件的 init() 和 start() 调用是由它的父组件的状态变化触发的,上层组件的初始化会触发子组件的初始化,上层组件的启动会触发子组件的启动,因此我们把组件的生命周期定义成一个个状态,把状态的转变看作是一个事件。而事件是有监听器的,在监听器里可以实现一些逻辑,并且监听器也可以方便的添加和删除,这就是典型的观察者模式。
具体来说就是在 LifeCycle 接口里加入两个方法:添加监听器和删除监听器。
在这里插入图片描述
重用性:LifeCycleBase 抽象基类
有了接口,我们就要用类去实现接口。一般来说实现类不止一个,不同的类在实现接口时往往会有一些相同的逻辑,如果让各个子类都去实现一遍,就会有重复代码。那子类如何重用这部分逻辑呢?
其实就是定义一个基类来实现共同的逻辑,然后让各个子类去继承它,就达到了重用的目的。而基类中往往会定义一些抽象方法,所谓的抽象方法就是说基类不会去实现这些方法,而是调用这些方法来实现骨架逻辑。抽象方法是留给各个子类去实现的,并且子类必须实现,否则无法实例化。
Tomcat 定义一个基类 LifeCycleBase 来实现 LifeCycle 接口,把一些公共的逻辑放到基类中去,比如生命状态的转变与维护、生命周期事件的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等方法。为了避免跟基类中的方法同名,我们把具体子类的实现方法改个名字,在后面加上 Internal,叫 initInternal()、startInternal() 等。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/151721.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux读写锁的容易犯的问题

Linux读写锁的容易犯的问题 读写锁是互斥锁之外的另一种用于多线程之间同步的一种方式。 多线程对于一个共享变量的读操作是安全的&#xff0c; 而写操作是不安全的。如果在一个读很多而写很少的场景之下&#xff0c;那么使用互斥锁将会阻碍大量的线程安全的读操作的进行。在…

腾讯云轻量和CVM有啥区别?怎么选择服务器配置?

腾讯云轻量服务器和云服务器有什么区别&#xff1f;为什么轻量应用服务器价格便宜&#xff1f;是因为轻量服务器CPU内存性能比云服务器CVM性能差吗&#xff1f;轻量应用服务器适合中小企业或个人开发者搭建企业官网、博客论坛、微信小程序或开发测试环境&#xff0c;云服务器CV…

关于Jupyter markdown的使用

一级标题 #空格 标题1 二级标题 ## 空格 标题2 三级标题 ###空格 标题3 无序&#xff1b; 有序&#xff1a; 数学符号&#xff1a;

记一次问题排查

1785年&#xff0c;卡文迪许在实验中发现&#xff0c;把不含水蒸气、二氧化碳的空气除去氧气和氮气后&#xff0c;仍有很少量的残余气体存在。这种现象在当时并没有引起化学家的重视。 一百多年后&#xff0c;英国物理学家瑞利测定氮气的密度时&#xff0c;发现从空气里分离出来…

Visual Studio 2019中的安全问题

最近&#xff0c;在使用Visual Studio 2019的时候遇到了一个很奇怪的问题&#xff0c;如下所示。 这里一直在说scanf函数不安全&#xff0c;导致报错&#xff0c;然后上网查了查相关资料&#xff0c;发现在代码中加那么一句就可以了&#xff0c;而且必须放在最前面。 #define …

网工内推 | IT高级运维工程师,周末双休,包吃包住,14-20k

01 深圳朗特智能控制股份有限公司 招聘岗位&#xff1a;IT高级运维工程师 职责描述&#xff1a; 1、对集团网络基础架构的建设、运维、安全制定相关标准和准则&#xff1b; 2、负责集团数据中心、核心设备、信息安全的管理和运维&#xff1b; 3、执行网络、服务器、核心交换机…

wpf中prism框架

安装prism包&#xff1a; 添加引用 using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows; using Prism.DryIoc; using Prism.Ioc;namespace PrismDemo …

A (1087) : DS单链表--类实现

Description 用C语言和类实现单链表&#xff0c;含头结点 属性包括&#xff1a;data数据域、next指针域 操作包括&#xff1a;插入、删除、查找 注意&#xff1a;单链表不是数组&#xff0c;所以位置从1开始对应首结点&#xff0c;头结点不放数据 类定义参考 #include<…

【考研复习】union有关的输出问题

文章目录 遇到的问题正确解答拓展参考文章 遇到的问题 首次遇到下面的代码时&#xff0c;感觉应该输出65,323。深入理解union的存储之后发现正确答案是&#xff1a;67,323. union {char c;int i; } u; int main(){u.c A;u.i 0x143;printf("%d,%d\n", u.c, u.i); …

初阶数据结构(四)带头双向链表

&#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;数据结构 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &#x1f339;&#x1f339;&#x1f339;关注我带你学习编程知识 带头双向链表 链表的相关介绍初始化链表销毁链…

XCode打包IOS应用发布App Store和Ad Hoc测试

文章目录 零、前置说明一、创建本地证书二、配置描述文件2.1 配置certificates2.1.1 配置证书2.1.2 安装cer证书2.1.2.1 打包机器和生成证书同机器2.1.2.2 打包机器和生成证书不同机器 2.2 创建Identifiers2.3 配置Devices2.4 配置Profiles2.4.1 配置生产Profile2.4.2 配置开发…

竞赛 机器视觉的试卷批改系统 - opencv python 视觉识别

文章目录 0 简介1 项目背景2 项目目的3 系统设计3.1 目标对象3.2 系统架构3.3 软件设计方案 4 图像预处理4.1 灰度二值化4.2 形态学处理4.3 算式提取4.4 倾斜校正4.5 字符分割 5 字符识别5.1 支持向量机原理5.2 基于SVM的字符识别5.3 SVM算法实现 6 算法测试7 系统实现8 最后 0…

【研究的艺术】通读《The Craft of Research》

通读《The Craft of Research》 前言1. 跟读者建立联系2. 明白问题的重要性3. 组织论述4. 论点4.1 Making Claims4.2 Assembling Reasons and Evidence4.3 Acknowledgments and Responses4.4 Warrants 未完待续。。。 前言 本篇博客是《The Craft of Research》的通读笔记&…

华为云云耀云服务器L实例评测|Elasticsearch的Docker版本的安装和参数设置 端口开放和浏览器访问

前言 最近华为云云耀云服务器L实例上新&#xff0c;也搞了一台来玩&#xff0c;期间遇到各种问题&#xff0c;在解决问题的过程中学到不少和运维相关的知识。 本篇博客介绍Elasticsearch的Docker版本的安装和参数设置&#xff0c;端口开放和浏览器访问。 其他相关的华为云云…

爱普生LQ1900KIIH复位方法

爱普生EPSON 1900KIIH是一部通用针式打印机&#xff0c;136列&#xff08;10cpi下&#xff09;的打印宽度&#xff0c;缓冲区128KB&#xff0c;打印速度为270字/秒。 打印机类型 打印方式&#xff1a;24针击打式点阵打印、打印方向&#xff1a;双向逻辑查找、安全规格标准&am…

jvm概述

1、JVM体系结构 2、JVM运行时数据区 3、JVM内存模型 JVM运行时内存 共享内存区 线程内存区 3.1、共享内存区 共享内存区 持久带(方法区 其他) 堆(Old Space Young Space(den S0 S1)) 持久代&#xff1a; JVM用持久带&#xff08;Permanent Space&#xff09;实现方法…

Aurora中的策略模式和模板模式

Aurora中的策略模式和模板模式 在aurora中为了方便以后的扩展使用了策略模式和模板模式实现图片上传和搜索功能&#xff0c;能够在配置类中设置使用Oss或者minio上传图片&#xff0c;es或者mysql文章搜索。后续有新的上传方式或者搜索方式只需要编写对应的实现类即可&#xff…

在原生html中使用less

引入less <link rel"stylesheet/less" href"./lessDemo.less" /><script src"./js/less.min.js"></script> less.min.js文件下载地址:https://github.com/less/less.js 注意&#xff1a;less文件在前&#xff0c;js文件在后…

深入理解强化学习——强化学习的基础知识

分类目录&#xff1a;《深入理解强化学习》总目录 在机器学习领域&#xff0c;有一类任务和人的选择很相似&#xff0c;即序贯决策&#xff08;Sequential Decision Making&#xff09;任务。决策和预测任务不同&#xff0c;决策往往会带来“后果”&#xff0c;因此决策者需要为…

C++ day2

自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void show() #include <ios…