跨境互联网 跨境互联网
首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)

Revin

首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)
  • Spring Data JPA
  • MyBatis

  • Spring

  • SpringBoot

  • Jdk

  • Tomcat

    • Tomcat架构设计&源码剖析
    • Apache Tomcat Web应用服务器
      • 1 Tomcat系统架构与原理剖析
        • 1.1 浏览器访问服务器的流程
        • 1.2 Tomcat 系统总体架构
        • 1.2.1 Tomcat请求处理⼤致过程
        • 1.2.2 Tomcat Servlet容器处理流程
        • 1.2.3 Tomcat系统总体架构
        • 1.3 Tomcat连接器组件Coyote
        • 1.3.1 Coyote简介
        • 1.3.2 Coyote的内部组件及流程
        • 1.4 Tomcat Servlet容器Catalina
        • 1.4.1 Tomcat模块分层结构图及Catalina位置
        • 1.4.2 Servlet*容器Catalina的结构
        • 1.4.3 Container组件的具体结构
      • 2 Tomcat服务器核⼼配置详解
      • 3 ⼿写实现迷你版Tomcat
      • 4 Tomcat源码构建及核⼼流程源码剖析
        • 4.1 源码构建
        • 4.1.1 下载源码
        • 4.1.2 源码导⼊IDE之前准备⼯作
        • 4.1.3 导⼊源码⼯程到IDE并进⾏配置
        • 4.2 核⼼流程源码剖析
      • 5 Tomcat类加载机制剖析
        • 5.1 JVM 的类加载机制
        • 5.2 双亲委派机制
        • 5.2.1 什么是双亲委派机制
        • 5.2.2 双亲委派机制的作⽤
        • 5.3 Tomcat 的类加载机制
      • 6 Tomcat对Https的⽀持及Tomcat性能优化策略
        • 6.1 Tomcat对HTTPS的⽀持
        • 6.1.1 HTTPS简介
        • 6.1.2 Tomcat对HTTPS的⽀持
        • 6.2 Tomcat性能优化策略
        • 6.2.1 虚拟机运⾏优化(参数调整)
        • 6.2.2 Tomcat配置调优
  • Netty

  • 若依

  • Traefik

  • Openresty

  • 开源框架
  • Tomcat
Revin
2023-08-05
目录

Apache Tomcat Web应用服务器

lg

说明:基于 8.5.50 版本的 Tomcat 讲解

# 1 Tomcat系统架构与原理剖析

b/s(浏览器/服务器模式) 浏览器是客户端(发送http请求) ———> 服务器端

# 1.1 浏览器访问服务器的流程

http请求的处理过程

Apache Tomcat Web应用服务器课程笔记_Page1_01

Apache Tomcat Web应用服务器课程笔记_Page2_01

注意:浏览器访问服务器使⽤的是Http协议,Http是应⽤层协议,⽤于定义数据通信的格式,具体的数据传输使⽤的是TCP/IP协议

# 1.2 Tomcat 系统总体架构

# 1.2.1 Tomcat请求处理⼤致过程

Tomcat是一个Http服务器(能够接收并且处理http请求,所以tomcat是一个http服务器)

我们使⽤浏览器向某⼀个⽹站发起请求,发出的是Http请求,那么在远程,Http服务器接收到这个请求之后,会调⽤具体的程序(Java类)进⾏处理,往往不同的请求由不同的Java类完成处理。

Apache Tomcat Web应用服务器课程笔记_Page2_02

Apache Tomcat Web应用服务器课程笔记_Page3_01

HTTP 服务器接收到请求之后把请求交给Servlet容器来处理,Servlet 容器通过Servlet接⼝调⽤业务类。Servlet接口和Servlet容器这一整套内容叫作Servlet规范。。

注意:Tomcat既按照Servlet规范的要求去实现了Servlet容器,同时它也具有HTTP服务器的功能。

Tomcat的两个重要身份

1)http服务器

2)Tomcat是⼀个Servlet容器

# 1.2.2 Tomcat Servlet容器处理流程

当⽤户请求某个URL资源时

1)HTTP服务器会把请求信息使⽤ServletRequest对象封装起来

2)进⼀步去调⽤Servlet容器中某个具体的Servlet

3)在 2)中,Servlet容器拿到请求后,根据URL和Servlet的映射关系,找到相应的Servlet

4)如果Servlet还没有被加载,就⽤反射机制创建这个Servlet,并调⽤Servlet的init⽅法来完成初始化

5)接着调⽤这个具体Servlet的service⽅法来处理请求,请求处理结果使⽤ServletResponse对象封装

6)把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端

Apache Tomcat Web应用服务器课程笔记_Page4_01

Apache Tomcat Web应用服务器课程笔记_Page4_02

# 1.2.3 Tomcat系统总体架构

通过上⾯的讲解,我们发现tomcat有两个⾮常重要的功能需要完成

1)和客户端浏览器进⾏交互,进⾏socket通信,将字节流和Request/Response等对象进⾏转换

2)Servlet容器处理业务逻辑

Apache Tomcat Web应用服务器课程笔记_Page5_01

Tomcat 设计了两个核⼼组件**连接器(Connector)和容器(Container)**来完成 Tomcat 的两⼤核⼼功能。

连接器,负责对外交流: 处理Socket连接,负责⽹络字节流与Request和Response对象的转化;

**容器,负责内部处理:**加载和管理Servlet,以及具体处理Request请求;

# 1.3 Tomcat连接器组件Coyote

# 1.3.1 Coyote简介

Coyote 是Tomcat 中连接器的组件名称 , 是对外的接⼝。客户端通过Coyote与服务器建⽴连接、发送请求并接受响应 。

(1)Coyote 封装了底层的⽹络通信(Socket 请求及响应处理)

(2)Coyote 使Catalina 容器(容器组件)与具体的请求协议及IO操作⽅式完全解耦

(3)Coyote 将Socket 输⼊转换封装为 Request 对象,进⼀步封装后交由Catalina 容器进⾏处理,处理请求完成后, Catalina 通过Coyote 提供的Response 对象将结果写⼊输出流

(4)Coyote 负责的是具体协议(应⽤层)和IO(传输层)相关内容

Apache Tomcat Web应用服务器课程笔记_Page6_01

Apache Tomcat Web应用服务器课程笔记_Page6_02

Tomcat Coyote ⽀持的 IO模型与协议

Tomcat⽀持多种应⽤层协议和I/O模型,如下:

Apache Tomcat Web应用服务器课程笔记_Page6_03

Apache Tomcat Web应用服务器课程笔记_Page7_01

在 8.0 之前 ,Tomcat 默认采⽤的I/O⽅式为 BIO,之后改为 NIO。 ⽆论 NIO、NIO2 还是 APR, 在性能⽅⾯均优于以往的BIO。 如果采⽤APR, 甚⾄可以达到 Apache HTTP Server 的影响性能。

# 1.3.2 Coyote的内部组件及流程

Apache Tomcat Web应用服务器课程笔记_Page7_02

Apache Tomcat Web应用服务器课程笔记_Page7_03

Coyote 组件及作⽤

image-20230805213721139

# 1.4 Tomcat Servlet容器Catalina

# 1.4.1 Tomcat模块分层结构图及Catalina位置

Tomcat是⼀个由⼀系列可配置(conf/server.xml)的组件构成的Web容器,⽽Catalina是Tomcat的servlet容器。

从另⼀个⻆度来说,Tomcat本质上就是⼀款Servlet容器, 因为 Catalina 才是 Tomcat 的核⼼ , 其他模块都是为Catalina 提供⽀撑的。 ⽐如 : 通过 Coyote 模块提供链接通信,Jasper 模块提供 JSP 引擎,Naming 提供JNDI 服务,Juli 提供⽇志服务。

Apache Tomcat Web应用服务器课程笔记_Page9_01

# 1.4.2 Servlet*容器Catalina的结构

Tomcat(我们往往有⼀个认识,Tomcat就是⼀个Catalina的实例,因为Catalina是Tomcat的核⼼)

Tomcat/Catalina实例

Apache Tomcat Web应用服务器课程笔记_Page9_02

其实,可以认为整个Tomcat就是⼀个Catalina实例,Tomcat 启动的时候会初始化这个实例,Catalina实例通过加载server.xml完成其他实例的创建,创建并管理⼀个Server,Server创建并管理多个服务,每个服务⼜可以有多个Connector和⼀个Container。

⼀个Catalina实例(容器)

⼀个 Server实例(容器)

多个Service实例(容器)

每⼀个Service实例下可以有多个Connector实例和⼀个Container实例

  • Catalina

负责解析Tomcat的配置⽂件(server.xml) , 以此来创建服务器Server组件并进⾏管理

  • Server

服务器表示整个Catalina Servlet容器以及其它组件,负责组装并启动Servlaet引擎,Tomcat连接器。Server通过实现Lifecycle接⼝,提供了⼀种优雅的启动和关闭整个系统的⽅式

  • Service

服务是Server内部的组件,⼀个Server包含多个Service。它将若⼲个Connector组件绑定到⼀个Container

  • Container

容器,负责处理⽤户的servlet请求,并返回对象给web⽤户的模块

# 1.4.3 Container组件的具体结构

Container组件下有⼏种具体的组件,分别是Engine、Host、Context和Wrapper。这4种组件(容器)是⽗⼦关系。Tomcat通过⼀种分层的架构,使得Servlet容器具有很好的灵活性。

  • Engine

表示整个Catalina的Servlet引擎,⽤来管理多个虚拟站点,⼀个Service最多只能有⼀个Engine,但是⼀个引擎可包含多个Host

  • Host

代表⼀个虚拟主机,或者说⼀个站点,可以给Tomcat配置多个虚拟主机地址,⽽⼀个虚拟主机下可包含多个Context

  • Context

表示⼀个Web应⽤程序, ⼀个Web应⽤可包含多个Wrapper

  • Wrapper

表示⼀个Servlet,Wrapper 作为容器中的最底层,不能包含⼦容器

上述组件的配置其实就体现在conf/server.xml中。

# 2 Tomcat服务器核⼼配置详解

问题⼀:去哪⼉配置? 核⼼配置在tomcat⽬录下conf/server.xml⽂件

问题⼆:怎么配置?

注意:

  • Tomcat 作为服务器的配置,主要是 server.xml ⽂件的配置;

  • server.xml中包含了 Servlet容器的相关配置,即 Catalina 的配置;

  • Xml ⽂件的讲解主要是标签的使⽤

主要标签结构如下:

<!--Server 根元素,创建⼀个Server实例,⼦标签有 Listener、GlobalNamingResources、Service-->
<Server>
    <!--定义监听器-->
    <Listener/>
    <!--定义服务器的全局JNDI资源 -->
    <GlobalNamingResources/>
    <!--定义⼀个Service服务,⼀个Server标签可以有多个Service服务实例-->
    <Service/>
</Server>
1
2
3
4
5
6
7
8
9

Server标签

<!--
port:关闭服务器的监听端⼝
shutdown:关闭服务器的指令字符串
-->
<Server port="8005" shutdown="SHUTDOWN">
    <!-- 以⽇志形式输出服务器 、操作系统、JVM的版本信息 -->
    <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
    <!-- Security listener. Documentation at /docs/config/listeners.html
<Listener className="org.apache.catalina.security.SecurityListener" />
-->
    <!--APR library loader. Documentation at /docs/apr.html -->
    <!-- 加载(服务器启动) 和 销毁 (服务器停⽌) APR。 如果找不到APR库, 则会输出⽇志, 并不影响 Tomcat启动 -->
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
    <!-- Prevent memory leaks due to use of particular java/javax APIs-->
    <!-- 避免JRE内存泄漏问题 -->
    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
    <!-- 加载(服务器启动) 和 销毁(服务器停⽌) 全局命名服务 -->
    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
    <!-- 在Context停⽌时重建 Executor 池中的线程, 以避免ThreadLocal 相关的内存泄漏 -->
    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
    <!-- Global JNDI resources Documentation at /docs/jndi-resources-howto.html GlobalNamingResources 中定义了全局命名服务-->
    <GlobalNamingResources>
        <!-- Editable user database that can also be used by UserDatabaseRealm to authenticate users-->
        <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" />
    </GlobalNamingResources>
    <!-- A "Service" is a collection of one or more "Connectors" that share a single "Container" Note: A "Service" is not itself a "Container", so you may not define subcomponents such as "Valves" at this level. Documentation at /docs/config/service.html-->
    <Service name="Catalina">
		...
</Service>
</Server>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Service标签

<!--

该标签⽤于创建 Service 实例,默认使⽤ org.apache.catalina.core.StandardService。

默认情况下,Tomcat 仅指定了Service 的名称, 值为 "Catalina"。

Service ⼦标签为 : Listener、Executor、Connector、Engine,

其中:

Listener ⽤于为Service添加⽣命周期监听器,

Executor ⽤于配置Service 共享线程池,

Connector ⽤于配置Service 包含的链接器,

Engine ⽤于配置Service中链接器对应的Servlet 容器引擎

-->
<Service name="Catalina">

...

</Service>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Executor标签

<!--

默认情况下,Service 并未添加共享线程池配置。 如果我们想添加⼀个线程池, 可以在<Service> 下添加如下配置:

name:线程池名称,⽤于 Connector中指定

namePrefix:所创建的每个线程的名称前缀,⼀个单独的线程名称为

namePrefix+threadNumber

maxThreads:池中最⼤线程数

minSpareThreads:活跃线程数,也就是核⼼池线程数,这些线程不会被销毁,会⼀直存在

maxIdleTime:线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为6000(1分钟),单位毫秒

maxQueueSize:在被执⾏前最⼤线程排队数⽬,默认为Int的最⼤值,也就是⼴义的⽆限。除⾮特殊情况,这个值 不需要更改,否则会有请求不会被处理的情况发⽣

prestartminSpareThreads:启动线程池时是否启动 minSpareThreads部分线程。默认值为false,即不启动

threadPriority:线程池中线程优先级,默认值为5,值从1到10

className:线程池实现类,未指定情况下,默认实现类为

org.apache.catalina.core.StandardThreadExecutor。如果想使⽤⾃定义线程池⾸先需要实现

org.apache.catalina.Executor接⼝

-->
<Executor name="commonThreadPool"

namePrefix="thread-exec-"

maxThreads="200"

minSpareThreads="100"

maxIdleTime="60000"

maxQueueSize="Integer.MAX_VALUE"

prestartminSpareThreads="false"

threadPriority="5"

className="org.apache.catalina.core.StandardThreadExecutor"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

Connector标签

Connector 标签⽤于创建链接器实例

默认情况下,server.xml 配置了两个链接器,⼀个⽀持HTTP协议,⼀个⽀持A JP协议⼤多数情况下,我们并不需要新增链接器配置,只是根据需要对已有链接器进⾏优化

<!--

port:
端⼝号,Connector ⽤于创建服务端Socket 并进⾏监听, 以等待客户端请求链接。如果该属性设置为0, Tomcat将会随机选择⼀个可⽤的端⼝号给当前Connector 使⽤

protocol:
当前Connector ⽀持的访问协议。 默认为 HTTP/1.1 , 并采⽤⾃动切换机制选择⼀个基于 JAVA NIO 的链接器或者基于本地APR的链接器(根据本地是否含有Tomcat的本地库判定)

connectionTimeOut:
Connector 接收链接后的等待超时时间, 单位为 毫秒。 -1 表示不超时。

redirectPort:
当前Connector 不⽀持SSL请求, 接收到了⼀个请求, 并且也符合security-constraint 约束,需要SSL传输,Catalina⾃动将请求重定向到指定的端⼝。

executor:
指定共享线程池的名称, 也可以通过maxThreads、minSpareThreads 等属性配置内部线程池。

URIEncoding:
⽤于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 , Tomcat7.x版本默认为ISO-8859-1

-->
<!--org.apache.coyote.http11.Http11NioProtocol , ⾮阻塞式 Java NIO 链接器-->
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

可以使⽤共享线程池

<Connector port="8080"

protocol="HTTP/1.1"

executor="commonThreadPool"

maxThreads="1000"

minSpareThreads="100"

acceptCount="1000"

maxConnections="1000"

connectionTimeout="20000"

compression="on"

compressionMinSize="2048"

disableUploadTimeout="true"

redirectPort="8443"

URIEncoding="UTF-8" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Engine标签

Engine 表示 Servlet 引擎

<!--

name: ⽤于指定Engine 的名称, 默认为Catalina

defaultHost:默认使⽤的虚拟主机名称, 当客户端请求指向的主机⽆效时, 将交由默认的虚拟主机处

理, 默认为localhost

-->
<Engine name="Catalina" defaultHost="localhost">

...

</Engine>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Host标签

Host 标签⽤于配置⼀个虚拟主机

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

...

</Host>
1
2
3
4
5

Context标签

Context 标签⽤于配置⼀个Web应⽤,如下:

<Host name="www.abc.com" appBase="webapps" unpackWARs="true" autoDeploy="true">
    <!--

docBase:Web应⽤⽬录或者War包的部署路径。可以是绝对路径,也可以是相对于 Host appBase的
相对路径。

path:Web应⽤的Context 路径。如果我们Host名为localhost, 则该web应⽤访问的根路径为:
http://localhost:8080/web_demo。

-->
    <Context docBase="/Users/yingdian/web_demo" path="/web3"></Context>
    <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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3 ⼿写实现迷你版Tomcat

名称:Minicat

Minicat要做的事情:作为⼀个服务器软件提供服务的,也即我们可以通过浏览器客户端发送http请求,

Minicat可以接收到请求进⾏处理,处理之后的结果可以返回浏览器客户端。

1)提供服务,接收请求(Socket通信)

2)请求信息封装成Request对象(Response对象)

3)客户端请求资源,资源分为静态资源(html)和动态资源(Servlet)

4)资源返回给客户端浏览器

我们递进式完成以上需求,提出V1.0、V2.0、V3.0版本的需求

V1.0需求:浏览器请求http://localhost:8080,返回⼀个固定的字符串到⻚⾯"Hello Minicat!"

V2.0需求:封装Request和Response对象,返回html静态资源⽂件

V3.0需求:可以请求动态资源(Servlet)

完成上述三个版本后,我们的代码如下

  • Bootstrap 启动类
package server;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
/**
 * Minicat的主类
 */
public class Bootstrap
{
    /**定义socket监听的端⼝号*/
    private int port = 8080;
    public int getPort()
    {
        return port;
    }
    public void setPort(int port)
    {
        this.port = port;
    }
    /**
     * Minicat启动需要初始化展开的⼀些操作
     */
    public void start() throws Exception
    {
        // 加载解析相关的配置,web.xml
        loadServlet();
        // 定义⼀个线程池
        int corePoolSize = 10;
        int maximumPoolSize = 50;
        long keepAliveTime = 100 L;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue < Runnable > workQueue = new ArrayBlockingQueue < > (50);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        RejectedExecutionHandler handler = new
        ThreadPoolExecutor.AbortPolicy();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
        /*

        完成Minicat 1.0版本

        需求:浏览器请求http://localhost:8080,返回⼀个固定的字符串到⻚

        ⾯"Hello Minicat!"

        */
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("=====>>>Minicat start on port:" + port);
        /*while(true) {

        Socket socket = serverSocket.accept();

        // 有了socket,接收到请求,获取输出流

        OutputStream outputStream = socket.getOutputStream();

        String data = "Hello Minicat!";

        String responseText =

        HttpProtocolUtil.getHttpHeader200(data.getBytes().length) + data;

        outputStream.write(responseText.getBytes());

        socket.close();

        }*/
        /**
         * 完成Minicat 2.0版本
         * 需求:封装Request和Response对象,返回html静态资源⽂件
         */
        /*while(true) {

        Socket socket = serverSocket.accept();

        InputStream inputStream = socket.getInputStream();



        // 封装Request对象和Response对象

        Request request = new Request(inputStream);

        Response response = new Response(socket.getOutputStream());



        response.outputHtml(request.getUrl());

        socket.close();

        }*/
        /**
         * 完成Minicat 3.0版本
         * 需求:可以请求动态资源(Servlet)
         */
        /*while(true) {

        Socket socket = serverSocket.accept();

        InputStream inputStream = socket.getInputStream();



        // 封装Request对象和Response对象

        Request request = new Request(inputStream);

        Response response = new Response(socket.getOutputStream());



        // 静态资源处理

        if(servletMap.get(request.getUrl()) == null) {

        response.outputHtml(request.getUrl());

        }else{

        // 动态资源servlet请求

        HttpServlet httpServlet =

        servletMap.get(request.getUrl());

        httpServlet.service(request,response);

        }



        socket.close();
        }

        */
        /*
        多线程改造(不使⽤线程池)
        */
        /*while(true) {

        Socket socket = serverSocket.accept();

        RequestProcessor requestProcessor = new

        RequestProcessor(socket,servletMap);

        requestProcessor.start();

        }*/
        System.out.println("=========>>>>>>使⽤线程池进⾏多线程改造");
        /*
        多线程改造(使⽤线程池)
        */
        while(true)
        {
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new
            RequestProcessor(socket, servletMap);
            //requestProcessor.start();
            threadPoolExecutor.execute(requestProcessor);
        }
    }
    private Map < String, HttpServlet > servletMap = new HashMap < String, HttpServlet > ();
    /**
     * 加载解析web.xml,初始化Servlet
     */
    private void loadServlet()
    {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        try
        {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List < Element > selectNodes = rootElement.selectNodes("//servlet");
            for(int i = 0; i < selectNodes.size(); i++)
            {
                Element element = selectNodes.get(i);
                // <servlet-name>lagou</servlet-name>
                Element servletnameElement = (Element)
                element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                // <servlet-class>server.LagouServlet</servlet-class>
                Element servletclassElement = (Element)
                element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();
                // 根据servlet-name的值找到url-pattern
                Element servletMapping = (Element)
                rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                // /lagou
                String urlPattern = servletMapping.selectSingleNode("url-pattern ").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
            }
        }
        catch (DocumentException e)
        {
            e.printStackTrace();
        }
        catch (IllegalAccessException e)
        {
            e.printStackTrace();
        }
        catch (InstantiationException e)
        {
            e.printStackTrace();
        }
        catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }
    }
    /**
     * Minicat 的程序启动⼊⼝
     * @param args
     */
    public static void main(String[] args)
    {
        Bootstrap bootstrap = new Bootstrap();
        try
        {
            // 启动Minicat
            bootstrap.start();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
  • Http协议⼯具类
package server;
/**
* http协议⼯具类,主要是提供响应头信息,这⾥我们只提供200和404的情况
*/
public class HttpProtocolUtil
{
    /**
    * 为响应码200提供请求头信息
    * @return
    */
    public static String getHttpHeader200(long contentLength)
    {
        return "HTTP/1.1 200 OK \n" + "Content-Type: text/html \n" + "Content-Length: " + contentLength + " \n" + "\r\n";
    }
    /**
    * 为响应码404提供请求头信息(此处也包含了数据内容)
    * @return
    */
    public static String getHttpHeader404()
    {
        String str404 = "<h1>404 not found</h1>";
        return "HTTP/1.1 404 NOT Found \n" + "Content-Type: text/html \n" + "Content-Length: " + str404.getBytes().length + " \n" + "\r\n" + str404;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Request封装类

package server;
import java.io.IOException;
import java.io.InputStream;
/**
* 把请求信息封装为Request对象(根据InputSteam输⼊流封装)
*/
public class Request
{
    private String method; // 请求⽅式,⽐如GET/POST
    private String url; // 例如 /,/index.html
    private InputStream inputStream; // 输⼊流,其他属性从输⼊流中解析出来
    public String getMethod()
    {
        return method;
    }
    public void setMethod(String method)
    {
        this.method = method;
    }
    public String getUrl()
    {
        return url;
    }
    public void setUrl(String url)
    {
        this.url = url;
    }
    public InputStream getInputStream()
    {
        return inputStream;
    }
    public void setInputStream(InputStream inputStream)
    {
        this.inputStream = inputStream;
    }
    public Request()
    {}
    // 构造器,输⼊流传⼊
    public Request(InputStream inputStream) throws IOException
    {
        this.inputStream = inputStream;
        // 从输⼊流中获取请求信息
        int count = 0;
        while(count == 0)
        {
            count = inputStream.available();
        }
        byte[] bytes = new byte[count];
        inputStream.read(bytes);
        String inputStr = new String(bytes);
        // 获取第⼀⾏请求头信息
        String firstLineStr = inputStr.split("\\n")[0]; // GET / HTTP/1.1
        String[] strings = firstLineStr.split(" ");
        this.method = strings[0];
        this.url = strings[1];
        System.out.println("=====>>method:" + method);
        System.out.println("=====>>url:" + url);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  • Response封装类
package server;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* 封装Response对象,需要依赖于OutputStream
*
* 该对象需要提供核⼼⽅法,输出html
*/
public class Response
{
    private OutputStream outputStream;
    public Response()
    {}
    public Response(OutputStream outputStream)
    {
        this.outputStream = outputStream;
    }
    // 使⽤输出流输出指定字符串
    public void output(String content) throws IOException
    {
        outputStream.write(content.getBytes());
    }
    /**
    *
    * @param path url,随后要根据url来获取到静态资源的绝对路径,进⼀步根据绝对路径读取该静态资源⽂件,最终通过
    * 输出流输出
    * /-----> classes
    */
    public void outputHtml(String path) throws IOException
    {
        // 获取静态资源⽂件的绝对路径
        String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
        // 输⼊静态资源⽂件
        File file = new File(absoluteResourcePath);
        if(file.exists() && file.isFile())
        {
            // 读取静态资源⽂件,输出静态资源
            StaticResourceUtil.outputStaticResource(new FileInputStream(file), outputStream);
        }
        else
        {
            // 输出404
            output(HttpProtocolUtil.getHttpHeader404());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  • 静态资源请求处理⼯具类
package server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class StaticResourceUtil
{
    /**
    * 获取静态资源⽂件的绝对路径
    * @param path
    * @return
    */
    public static String getAbsolutePath(String path)
    {
        String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
        return absolutePath.replaceAll("\\\\", "/") + path;
    }
    /**
    * 读取静态资源⽂件输⼊流,通过输出流输出
    */
    public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException
    {
        int count = 0;
        while(count == 0)
        {
            count = inputStream.available();
        }
        int resourceSize = count;
        // 输出http请求头,然后再输出具体内容
        outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getByt es());
        // 读取内容输出
        long written = 0; // 已经读取的内容⻓度
        int byteSize = 1024; // 计划每次缓冲的⻓度
        byte[] bytes = new byte[byteSize];
        while(written < resourceSize)
        {
            if(written + byteSize > resourceSize)
            { // 说明剩余未读取⼤⼩不
                ⾜⼀
                个1024⻓ 度, 那就按真实⻓ 度处理
                byteSize = (int)(resourceSize - written); // 剩余的⽂件内容
                ⻓
                度
                bytes = new byte[byteSize];
            }
            inputStream.read(bytes);
            outputStream.write(bytes);
            outputStream.flush();
            written += byteSize;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  • 动态资源请求

Servlet接⼝定义

package server;
public interface Servlet
{
    void init() throws Exception;
    void destory() throws Exception;
    void service(Request request, Response response) throws Exception;
}
1
2
3
4
5
6
7

HttpServlet抽象类定义

package server;
public abstract class HttpServlet implements Servlet
{
    public abstract void doGet(Request request, Response response);
    public abstract void doPost(Request request, Response response);
    @Override
    public void service(Request request, Response response) throws
    Exception
    {
        if("GET".equalsIgnoreCase(request.getMethod()))
        {
            doGet(request, response);
        }
        else
        {
            doPost(request, response);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

业务类Servlet定义LagouServlet

package server;
import java.io.IOException;
public class LagouServlet extends HttpServlet
{
    @Override
    public void doGet(Request request, Response response)
    {
        try
        {
            Thread.sleep(100000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        String content = "<h1>LagouServlet get</h1>";
        try
        {
            response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
    @Override
    public void doPost(Request request, Response response)
    {
        String content = "<h1>LagouServlet post</h1>";
        try
        {
            response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
    @Override
    public void init() throws Exception
    {}
    @Override
    public void destory() throws Exception
    {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  • 多线程改造封装的RequestProcessor类
package server;
import java.io.InputStream;
import java.net.Socket;
import java.util.Map;
public class RequestProcessor extends Thread
{
    private Socket socket;
    private Map < String, HttpServlet > servletMap;
    public RequestProcessor(Socket socket, Map < String, HttpServlet > servletMap)
    {
        this.socket = socket;
        this.servletMap = servletMap;
    }
    @Override
    public void run()
    {
        try
        {
            InputStream inputStream = socket.getInputStream();
            // 封装Request对象和Response对象
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());
            // 静态资源处理
            if(servletMap.get(request.getUrl()) == null)
            {
                response.outputHtml(request.getUrl());
            }
            else
            {
                // 动态资源servlet请求
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request, response);
            }
            socket.close();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 4 Tomcat源码构建及核⼼流程源码剖析

# 4.1 源码构建

# 4.1.1 下载源码

Apache Tomcat Web应用服务器课程笔记_Page28_01

# 4.1.2 源码导⼊IDE之前准备⼯作

  • 解压 tar.gz 压缩包,得到⽬录 apache-tomcat-8.5.50-src

  • 进⼊ apache-tomcat-8.5.50-src ⽬录,创建⼀个pom.xml⽂件,⽂件内容如下

<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>apache-tomcat-8.5.50-src</artifactId>
    <name>Tomcat8.5</name>
    <version>8.5</version>
    <build>
        <!--指定源⽬录-->
        <finalName>Tomcat8.5</finalName>
        <sourceDirectory>java</sourceDirectory>
        <resources>
            <resource>
                <directory>java</directory>
            </resource>
        </resources>
        <plugins>
            <!--引⼊编译插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <!--tomcat 依赖的基础包-->
    <dependencies>
        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>ant</groupId>
            <artifactId>ant</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
            <version>1.6.2</version>
        </dependency>
        <dependency>
            <groupId>javax.xml</groupId>
            <artifactId>jaxrpc</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jdt.core.compiler</groupId>
            <artifactId>ecj</artifactId>
            <version>4.5.1</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.soap</groupId>
            <artifactId>javax.xml.soap-api</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
  • 在 apache-tomcat-8.5.50-src ⽬录中创建 source ⽂件夹

  • 将 conf、webapps ⽬录移动到刚刚创建的 source ⽂件夹中

# 4.1.3 导⼊源码⼯程到IDE并进⾏配置

  • 将源码⼯程导⼊到 IDEA 中

  • 给 tomcat 的源码程序启动类 Bootstrap 配置 VM 参数,因为 tomcat 源码运⾏也需要加载配置⽂件等。

-Dcatalina.home=/Users/yingdian/workspace/servers/apache-tomcat-8.5.50-src/source
-Dcatalina.base=/Users/yingdian/workspace/servers/apache-tomcat-8.5.50-src/source
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=/Users/yingdian/workspace/servers/apache-tomcat-8.5.50-src/source/conf/logging.properties
1
2
3
4

Apache Tomcat Web应用服务器课程笔记_Page31_01

运⾏ Bootstrap 类的 main 函数,此时就启动了tomcat,启动时候会去加载所配置的 conf ⽬录下的server.xml等配置⽂件,所以访问8080端⼝即可,但此时我们会遇到如下的⼀个错误

Apache Tomcat Web应用服务器课程笔记_Page31_02

原因是Jsp引擎Jasper没有被初始化,从⽽⽆法编译JSP,我们需要在tomcat的源码ContextConfig类中的configureStart⽅法中增加⼀⾏代码将 Jsp 引擎初始化,如下

Apache Tomcat Web应用服务器课程笔记_Page32_01

重启 Tomcat,正常访问即可。⾄此,Tomcat 源码构建完毕。

# 4.2 核⼼流程源码剖析

Tomcat中的各容器组件都会涉及创建、销毁等,因此设计了⽣命周期接⼝Lifecycle进⾏统⼀规范,各容器组件实现该接⼝。

Lifecycle⽣命周期接⼝主要⽅法示意

Apache Tomcat Web应用服务器课程笔记_Page32_02

Lifecycle⽣命周期接⼝继承体系示意

Apache Tomcat Web应用服务器课程笔记_Page33_01

核⼼流程源码剖析

源码追踪部分我们关注两个流程:Tomcat启动流程和Tomcat请求处理流程

Tomcat启动流程

Apache Tomcat Web应用服务器课程笔记_Page34_01

Tomcat请求处理流程

  • 请求处理流程分析

Apache Tomcat Web应用服务器课程笔记_Page34_02

  • 请求处理流程示意图

Apache Tomcat Web应用服务器课程笔记_Page35_01

  • Mapper组件体系结构

Apache Tomcat Web应用服务器课程笔记_Page35_02

# 5 Tomcat类加载机制剖析

Java类(.java)—> 字节码⽂件(.class) —> 字节码⽂件需要被加载到jvm内存当中(这个过程就是⼀个类加载的过程)

类加载器(ClassLoader,说⽩了也是⼀个类,jvm启动的时候先把类加载器读取到内存当中去,其他的类(⽐如各种jar中的字节码⽂件,⾃⼰开发的代码编译之后的.class⽂件等等))

要说 Tomcat 的类加载机制,⾸先需要来看看 Jvm 的类加载机制,因为 Tomcat 类加载机制是在 Jvm 类加载机制基础之上进⾏了⼀些变动。

# 5.1 JVM 的类加载机制

JVM 的类加载机制中有⼀个⾮常重要的⻆⾊叫做类加载器(ClassLoader),类加载器有⾃⼰的体系,Jvm内置了⼏种类加载器,包括:引导类加载器、扩展类加载器、系统类加载器,他们之间形成⽗⼦关系,通过 Parent 属性来定义这种关系,最终可以形成树形结构。

Apache Tomcat Web应用服务器课程笔记_Page36_01

Apache Tomcat Web应用服务器课程笔记_Page37_01

image-20230805221457109

另外:⽤户可以⾃定义类加载器(Java编写,⽤户⾃定义的类加载器,可加载指定路径的 class⽂件

当 JVM 运⾏过程中,⽤户⾃定义了类加载器去加载某些类时,会按照下⾯的步骤(⽗类委托机制)

1) ⽤户⾃⼰的类加载器,把加载请求传给⽗加载器,⽗加载器再传给其⽗加载器,⼀直到加载器树的顶层

2 )最顶层的类加载器⾸先针对其特定的位置加载,如果加载不到就转交给⼦类

3 )如果⼀直到底层的类加载都没有加载到,那么就会抛出异常 ClassNotFoundException

因此,按照这个过程可以想到,如果同样在 classpath 指定的⽬录中和⾃⼰⼯作⽬录中存放相同的class,会优先加载 classpath ⽬录中的⽂件

# 5.2 双亲委派机制

# 5.2.1 什么是双亲委派机制

当某个类加载器需要加载某个.class⽂件时,它⾸先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,⾃⼰才会去加载这个类。

# 5.2.2 双亲委派机制的作⽤

  • 防⽌重复加载同⼀个.class。通过委托去向上⾯问⼀问,加载过了,就不⽤再加载⼀遍。保证数据安全。

  • 保证核⼼.class不能被篡改。通过委托⽅式,不会去篡改核⼼.class,即使篡改也不会去加载,即使加载也不会是同⼀个.class对象了。不同的加载器加载同⼀个.class也不是同⼀个.class对象。这样保证了class执⾏安全(如果⼦类加载器先加载,那么我们可以写⼀些与java.lang包中基础类同名的类, 然后再定义⼀个⼦类加载器,这样整个应⽤使⽤的基础类就都变成我们⾃⼰定义的类了。)Object类 -----> ⾃定义类加载器(会出现问题的,那么真正的Object类就可能被篡改了)

# 5.3 Tomcat 的类加载机制

Tomcat 的类加载机制相对于 Jvm 的类加载机制做了⼀些改变。

没有严格的遵从双亲委派机制,也可以说打破了双亲委派机制

⽐如:有⼀个tomcat,webapps下部署了两个应⽤

app1/lib/a-1.0.jar com.lagou.edu.Abc

app2/lib/a-2.0.jar com.lagou.edu.Abc
1
2
3

不同版本中Abc类的内容是不同的,代码是不⼀样的

Apache Tomcat Web应用服务器课程笔记_Page39_01

  • 引导类加载器 和 扩展类加载器 的作⽤不变

  • 系统类加载器正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使⽤该变量,⽽是加载tomcat启动的类,⽐如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下

  • Common 通⽤类加载器加载Tomcat使⽤以及应⽤通⽤的⼀些类,位于CATALINA_HOME/lib下,⽐如servlet-api.jar

  • Catalina ClassLoader ⽤于加载服务器内部可⻅类,这些类应⽤程序不能访问

  • Shared ClassLoader ⽤于加载应⽤程序共享类,这些类服务器不会依赖

  • Webapp ClassLoader,每个应⽤程序都会有⼀个独⼀⽆⼆的Webapp ClassLoader,他⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。

    tomcat 8.5 默认改变了严格的双亲委派机制

    • ⾸先从 Bootstrap Classloader加载指定的类
    • 如果未加载到,则从 /WEB-INF/classes加载
    • 如果未加载到,则从 /WEB-INF/lib/*.jar 加载
    • 如果未加载到,则依次从 System、Common、Shared 加载(在这最后⼀步,遵从双亲委派机制)

# 6 Tomcat对Https的⽀持及Tomcat性能优化策略

# 6.1 Tomcat对HTTPS的⽀持

Https是⽤来加强数据传输安全的

# 6.1.1 HTTPS简介

Apache Tomcat Web应用服务器课程笔记_Page40_01

Http超⽂本传输协议,明⽂传输 ,传输不安全,https在传输数据的时候会对数据进⾏加密

ssl协议

TLS(transport layer security)协议

HTTPS和HTTP的主要区别

  • HTTPS协议使⽤时需要到电⼦商务认证授权机构(CA)申请SSL证书

  • HTTP默认使⽤8080端⼝,HTTPS默认使⽤8443端⼝

  • HTTPS则是具有SSL加密的安全性传输协议,对数据的传输进⾏加密,效果上相当于HTTP的升级版

  • HTTP的连接是⽆状态的,不安全的;HTTPS协议是由SSL+HTTP协议构建的可进⾏加密传输、身份认证的⽹络协议,⽐HTTP协议安全

HTTPS⼯作原理

Apache Tomcat Web应用服务器课程笔记_Page41_01

# 6.1.2 Tomcat对HTTPS的⽀持

1) 使⽤ JDK 中的 keytool ⼯具⽣成免费的秘钥库⽂件(证书)。

keytool -genkey -alias lagou -keyalg RSA -keystore lagou.keystore
1

Apache Tomcat Web应用服务器课程笔记_Page41_02

2) 配置conf/server.xml

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"

maxThreads="150" schema="https" secure="true" SSLEnabled="true">
    <SSLHostConfig>
        <Certificate

certificateKeystoreFile="/Users/yingdian/workspace/servers/apache-tomcat-

8.5.50/conf/lagou.keystore" certificateKeystorePassword="lagou123" type="RSA"

/>
    </SSLHostConfig>
</Connector>
1
2
3
4
5
6
7
8
9
10
11
12
13

4)使⽤https协议访问8443端⼝(https://localhost:8443)。

# 6.2 Tomcat性能优化策略

系统性能的衡量指标,主要是响应时间和吞吐量。

1)响应时间:执⾏某个操作的耗时;

1)吞吐量:系统在给定时间内能够⽀持的事务数量,单位为TPS(Transactions PerSecond的缩写,也就是事务数/秒,⼀个事务是指⼀个客户机向服务器发送请求然后服务器做出反应的过程。

Tomcat优化从两个⽅⾯进⾏

1)JVM虚拟机优化(优化内存模型)

2)Tomcat⾃身配置的优化(⽐如是否使⽤了共享线程池?IO模型?)

学习优化的原则

提供给⼤家优化思路,没有说有明确的参数值⼤家直接去使⽤,必须根据⾃⼰的真实⽣产环境来进⾏调整,调优是⼀个过程

# 6.2.1 虚拟机运⾏优化(参数调整)

Java 虚拟机的运⾏优化主要是内存分配和垃圾回收策略的优化:

  • 内存直接影响服务的运⾏效率和吞吐量

  • 垃圾回收机制会不同程度地导致程序运⾏中断(垃圾回收策略不同,垃圾回收次数和回收效率都是不同的)

1) Java 虚拟机内存相关参数

image-20230805222245890

JVM内存模型回顾

Apache Tomcat Web应用服务器课程笔记_Page43_01

参数调整示例

JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
1

调整后查看可使⽤JDK提供的内存映射⼯具

Apache Tomcat Web应用服务器课程笔记_Page44_01

2) 垃圾回收(GC)策略

垃圾回收性能指标

  • 吞吐量:⼯作时间(排除GC时间)占总时间的百分⽐, ⼯作时间并不仅是程序运⾏的时间,还包含内存分配时间。

  • 暂停时间:由垃圾回收导致的应⽤程序停⽌响应次数/时间。

垃圾收集器

  • 串⾏收集器(Serial Collector)

单线程执⾏所有的垃圾回收⼯作, 适⽤于单核CPU服务器

⼯作进程-----|(单线程)垃圾回收线程进⾏垃圾收集|---⼯作进程继续

  • 并⾏收集器(Parallel Collector)

⼯作进程-----|(多线程)垃圾回收线程进⾏垃圾收集|---⼯作进程继续

⼜称为吞吐量收集器(关注吞吐量), 以并⾏的⽅式执⾏年轻代的垃圾回收, 该⽅式可以显著降低垃圾回收的开销(指多条垃圾收集线程并⾏⼯作,但此时⽤户线程仍然处于等待状态)。适⽤于多处理器或多线程硬件上运⾏的数据量较⼤的应⽤

  • 并发收集器(Concurrent Collector)

以并发的⽅式执⾏⼤部分垃圾回收⼯作,以缩短垃圾回收的暂停时间。适⽤于那些响应时间优先于吞吐量的应⽤, 因为该收集器虽然最⼩化了暂停时间(指⽤户线程与垃圾收集线程同时执⾏,但不⼀定是并⾏的,可能会交替进⾏), 但是会降低应⽤程序的性能

  • CMS收集器(Concurrent Mark Sweep Collector)

并发标记清除收集器, 适⽤于那些更愿意缩短垃圾回收暂停时间并且负担的起与垃圾回收共享处理器资源的应⽤

  • G1收集器(Garbage-First Garbage Collector)

适⽤于⼤容量内存的多核服务器, 可以在满⾜垃圾回收暂停时间⽬标的同时, 以最⼤可能性实现⾼吞吐量(JDK1.7之后)

垃圾回收器参数

image-20230805222522408

在bin/catalina.sh的脚本中 , 追加如下配置 :

JAVA_OPTS="-XX:+UseConcMarkSweepGC"
1

# 6.2.2 Tomcat配置调优

Tomcat⾃身相关的调优

  • 调整tomcat线程池

Apache Tomcat Web应用服务器课程笔记_Page46_01

  • 调整tomcat的连接器

调整tomcat/conf/server.xml 中关于链接器的配置可以提升应⽤服务器的性能。

image-20230805222612641

  • 禁⽤ A JP 连接器

    Apache Tomcat Web应用服务器课程笔记_Page46_02

  • 调整 IO 模式

Tomcat8之前的版本默认使⽤BIO(阻塞式IO),对于每⼀个请求都要创建⼀个线程来处理,不适合⾼并发;Tomcat8以后的版本默认使⽤NIO模式(⾮阻塞式IO)

Apache Tomcat Web应用服务器课程笔记_Page46_03

当Tomcat并发性能有较⾼要求或者出现瓶颈时,我们可以尝试使⽤APR模式,APR(Apache Portable Runtime)是从操作系统级别解决异步IO问题,使⽤时需要在操作系统上安装APR和Native(因为APR原理是使⽤使⽤JNI技术调⽤操作系统底层的IO接⼝)

  • 动静分离

可以使⽤Nginx+Tomcat相结合的部署⽅案,Nginx负责静态资源访问,Tomcat负责Jsp等动态资源访问处理(因为Tomcat不擅⻓处理静态资源)。

上次更新: 2025/04/03, 11:07:08
Tomcat架构设计&源码剖析
Netty是什么?

← Tomcat架构设计&源码剖析 Netty是什么?→

最近更新
01
tailwindcss
03-26
02
PaddleSpeech
02-18
03
whisper
02-18
更多文章>
Theme by Vdoing | Copyright © 2019-2025 跨境互联网 | 豫ICP备14016603号-5 | 豫公网安备41090002410995号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式