摘要:本文介绍了Tomcat的基本使用和配置。
环境
Windows 10 企业版 LTSC 21H2
Java 1.8
Tomcat 8.5.50
1 概述
1.1 背景
在Servlet容器出现之前,如果想通过Java实现一个简单的Web服务,需要开发者手动完成所有底层工作:
- 编写Socket代码,监听指定端口。
- 接收客户端的TCP连接,使用线程池处理并发请求。
- 解析HTTP请求报文,处理会话信息。
- 生成HTTP响应报文,发送给客户端。
- 管理服务的声明周期,关闭资源和线程。
这些工作不仅极其繁琐,而且需要深厚的编程功底,开发者根本无法专注于业务逻辑。因此迫切需要使用某种技术管理所有底层工作,让开发者专注于业务逻辑。
使用Servlet容器主要是为了解决Web服务的构建和管理问题,它替开发者封装了所有Web开发的底层细节。
1.2 简介
Tomcat是Apache基金会旗下的开源轻量级Servlet容器,也是JavaWeb开发中最主流的容器之一。
Tomcat的核心优势是规范兼容性强、生态完善、稳定性高,与主流JavaWeb框架深度适配,文档和社区解决方案丰富,部署简单且无商业授权成本,既适合开发测试环境,也能支撑中小型生产环境的JavaWeb应用。
1.3 比较
核心定位:
- Tomcat:Apache旗下的开源轻量级Servlet容器,JavaWeb开发的事实标准,主打稳定性和标准规范实现。
- Jetty:Eclipse基金维护的轻量级嵌入式Web容器,以灵活集成和异步IO为核心优势,适合现代架构。
- Resin:Caucho公司开发的全栈式JavaEE应用服务器,支持完整企业级规范,侧重商业性能优化。
架构特性:
- Tomcat:模块化设计,结构清晰但相对偏重,规范适配性强。
- Jetty:事件驱动和异步非阻塞架构,组件高度可插拔,极度轻量化,启动迅速。
- Resin:一体化集成架构,内置缓存和负载均衡等企业级特性,JSP编译优化突出。
性能特点:
- Tomcat:性能均衡稳定,中等并发场景表现可靠,高并发异步场景资源消耗相对较高。
- Jetty:高并发和异步IO性能卓越,启动速度极快,内存占用极低,WebSocket支持优异。
- Resin:高并发企业级场景表现突出,JSP执行效率高,通过内置优化提升响应速度。
适用场景:
- Tomcat:绝大多数中小型应用,开发测试环境,主流框架默认适配。
- Jetty:嵌入式部署、微服务架构、实时通信应用,需要快速启停的测试和云原生场景。
- Resin:大型电商、高流量企业级应用,需要集群部署和负载均衡的生产环境。
使用成本:
- Tomcat:社区活跃度极高,文档全面丰富,入门门槛低,解决方案随处可见。
- Jetty:社区活跃度中等,文档精简,需要一定定制化能力,学习曲线稍陡。
- Resin:社区活跃度低,开源资料有限,配置运维复杂,适合有专业运维团队的企业。
授权方式:
- Tomcat:基于Apache 2.0协议,完全开源免费,无商业授权成本。
- Jetty:基于Apache 2.0/EPL双协议,完全开源免费,无商业授权成本。
- Resin:基于GPL协议,基础版开源,企业级特性需商业授权付费。
1.4 版本
版本对照:
| Tomcat版本 | Servlet版本 | Java版本 |
|---|---|---|
| Tomcat 9.0.x | Servlet 4.0 | Java 8+ |
| Tomcat 8.5.x | Servlet 3.1 | Java 7+ |
| Tomcat 8.0.x | Servlet 3.1 | Java 7+ |
| Tomcat 7.0.x | Servlet 3.0 | Java 6+ |
| Tomcat 6.0.x | Servlet 2.5 | Java 5+ |
| Tomcat 5.5.x | Servlet 2.4 | Java 5+ |
2 使用
2.1 下载
官方网站:https://tomcat.apache.org/
历史版本:https://archive.apache.org/dist/tomcat/
版本说明:
- apache-tomcat-x.zip:Windows系统的压缩包,不包含批处理脚本以及本地库。
- apache-tomcat-x.tar.gz:Linux系统的压缩包,主要提供给Linux系统使用。
- apache-tomcat-x.exe:Windows系统的安装包,功能和压缩包基本一致,适用Windows快捷键以及系统服务形式启动。
- apache-tomcat-x-windows-x86.zip:32位Windows发布包,包含批处理脚本以及本地库,适配32位和64位操作系统。
- apache-tomcat-x-windows-x64.zip:64位Windows发布包,包含批处理脚本以及本地库,只适配64位操作系统。
2.2 安装
在安装前需要确保配置了JDK环境变量,即配置了JAVA_HOME环境变量。
将apache-tomcat-x.zip压缩包解压到指定目录,即可完成Tomcat的安装。
进入解压目录,可以看到Tomcat的目录结构:
1 | apache-tomcat-x |
目录说明:
- bin:存放Tomcat的可执行脚本。
- conf:存放Tomcat的配置文件。
- lib:存放Tomcat的核心类库和依赖包。
- logs:存放Tomcat的日志文件。
- temp:存放Tomcat的临时文件。
- webapps:存放Tomcat的Web应用程序,启动时会加载该目录下的应用程序,默认访问ROOT项目。
- work:存放Tomcat的运行时文件。
进入解压目录,执行bin/startup.bat启动Tomcat,执行bin/shutdown.bat停止Tomcat。
启动Tomcat后,访问http://localhost:8080/即可看到Tomcat默认页面。
3 架构
3.1 总体
Tomcat在实现Servlet容器规范时,核心拆分为两大模块:
- HTTP服务器(Connector):接收客户端的请求,解析请求报文,生成响应报文,发送给客户端。
- Servlet容器(Container):管理Servlet全生命周期,接收处理后的请求对象,处理会话信息,执行业务逻辑,返回响应数据。
模块划分:
说明:
- Server:Tomcat运行的示例。一个Tomcat服务器实例包含一个Server组件。
- Service:提供完整的Web服务。一个Server组件包含多个Service组件,但是多个Service组件不能存在相同的Connector组件。
- Connector:根据配置的协议处理对应的请求。一个Service组件包含多个Connector组件,但是Connector组件不能相同。
- Container:处理请求并生成响应。一个Service组件包含一个Container组件。
3.2 详细
3.2.1 连接器
将连接器的功能进一步细分:
- 监听网络端口,接收网络请求,将字节流转为Request对象。
- 将Request对象转为ServletRequest对象传递给容器。
- 将容器返回的ServletResponse对象转为Response对象。
- 将Response对象转为字节流,生成响应报文,发送给客户端。
连接器组成:
说明:
- Endpoint:通信端点,具体的Socket接收处理类。
- Processor:协议处理接口,构造Request对象和Response对象。
- ProtocolHandler:协议处理器,封装了Endpoint组件和Processor组件,实现具体的协议处理逻辑。
- Adapter:适配器,将Request对象转为ServletRequest对象,将ServletResponse对象转为Response对象。
3.2.2 容器
容器中存在多个层次:
说明:
- Engine:引擎,用于管理多个Host虚拟主机。一个Service组件包含一个Engine组件。
- Host:虚拟主机,设置站点相关信息。一个Engine组件包含多个Host组件。
- Context:上下文,为Wrapper组件提供运行环境。一个Host组件包含多个Context组件。
- Wrapper:包装器,用于管理Servlet生命周期。一个Context组件包含多个Wrapper组件。
4 流程
4.1 启动脚本
通过启动脚本启动Tomcat服务。
脚本调用Bootstrap类的main()方法,这是启动的入口:
1 | // org.apache.catalina.startup.Bootstrap |
4.2 创建类加载器
调用Bootstrap的init()方法:
1 | public void init() throws Exception { |
调用Bootstrap的initClassLoaders()方法:
1 | private void initClassLoaders() { |
4.3 破坏双亲委派机制
双亲委派机制是Java类加载器的一种机制,用于保证Java核心库的安全性。
简单来说,当一个类加载器需要加载某个类时,它会先去自己的类路径中查找,如果没有找到,就去父类加载器的类路径中查找,以此类推,直到找到为止。
但是在某些情况下,开发者需要破坏这种机制,实现自定义的类加载逻辑。
如果Tomcat使用双亲委派机制来加载类,在多个Web应用中部署相同的类,但版本不同时,就会因为版本不同而导致问题。
为了解决多个Web应用的部署问题,在Tomcat中就破坏了双亲委派机制,目的是为了提高灵活性与隔离性:
- 应用隔离:确保不同的Web应用互不影响,不会因为版本问题导致冲突。
- 资源共享:让服务器的核心类库和Web应用的通用类库可以安全共享,避免重复加载。
- 容器隔离:确保核心类库和Web应用的类库隔离,保证容器安全稳定。
- 热部署:在不重启服务器的情况下,丢弃旧的类加载器,然后创建新的类加载器重新加载应用,从而实现热部署,这是双亲委派机制做不到的。
Tomcat的类加载机制如下:
说明:
- 启动类加载器:用C++编写的,是JVM自带的类装载器,负责Java平台核心库,用来装载核心类库。无法直接获取。
- 扩展类加载器:负责
jre/lib/ext目录下的Jar包或–D java.ext.dirs指定目录下的Jar包装入工作库。 - 应用程序类加载器:负责
java –classpath或–D java.class.path所指的目录下的类与Jar包装入工作,最常用的加载器。 - Common类加载器:负责加载Tomcat安装目录的
lib目录下的Jar包,对Tomcat和所有Web应用都可见。 - Catalina类加载器:负责加载Tomcat私有的类,对Web应用不可见。
- Shared类加载器:负责加载Web应用共享的类,对Tomcat不可见。
- WebApp类加载器:负责加载Web应用私有的类,对Tomcat和其他Web应用都不可见。打破双亲委派机制。
- JSP类加载器:每个JSP页面私有的类加载器,对Tomcat和所有Web应用都不可见。当JSP页面修改后,会创建新的类加载器。
4.4 逐级初始化
调用Bootstrap的load()方法:
1 | private void load(String[] arguments) throws Exception { |
调用Catalina的load()方法:
1 | public void load() { |
调用StandardServer的init()方法,实际调用LifecycleBase的init()方法:
1 | public final synchronized void init() throws LifecycleException { |
调用StandardServer的initInternal()方法,初始化Service组件:
1 | protected void initInternal() throws LifecycleException { |
调用StandardService的initInternal()方法,初始化其他组件:
1 | protected void initInternal() throws LifecycleException { |
调用Connector的initInternal()方法,初始化ProtocolHandler组件:
1 | protected void initInternal() throws LifecycleException { |
调用AbstractProtocol的init()方法,初始化Endpoint组件:
1 | public void init() throws Exception { |
调用AbstractEndpoint的init()方法:
1 | public void init() throws Exception { |
4.5 逐级启动
调用Bootstrap的start()方法:
1 | public void start() throws Exception { |
调用Catalina的start()方法:
1 | public void start() { |
调用StandardServer的start()方法,实际调用LifecycleBase的start()方法:
1 | public final synchronized void start() throws LifecycleException { |
调用StandardServer的startInternal()方法,启动Service组件:
1 | protected void startInternal() throws LifecycleException { |
调用StandardService的startInternal()方法,启动其他组件:
1 | protected void startInternal() throws LifecycleException { |
调用Connector的startInternal()方法,启动ProtocolHandler组件:
1 | protected void startInternal() throws LifecycleException { |
调用AbstractProtocol的start()方法,启动Endpoint组件:
1 | public void start() throws Exception { |
调用AbstractEndpoint的start()方法:
1 | public final void start() throws Exception { |
调用NioEndpoint的startInternal()方法:
1 | public void startInternal() throws Exception { |
5 配置
5.1 修改端口
进入解压目录,打开conf/server.xml文件,找到:
1 | <Connector port="8080" protocol="HTTP/1.1" |
修改port属性为新的端口号。
5.2 命令行乱码
进入解压目录,打开conf/logging.properties文件,找到:
1 | java.util.logging.ConsoleHandler.encoding = UTF-8 |
修改encoding属性为GBK后,保存并重启。
5.3 修改管理密码
进入解压目录,打开conf/tomcat-users.xml文件,在tomcat-users标签中增加:
1 | <role rolename="manager-gui"/> |
保存并重启,通过用户和密码访问管理页面。
5.4 默认映射
5.4.1 处理静态资源
进入解压目录,打开conf/web.xml配置文件,找到:
1 | <servlet> |
如果没有配置处理静态资源的Servlet映射,会使用DefaultServlet在Web应用的目录结构中查找对应的静态资源文件。
5.4.2 处理JSP页面
进入解压目录,打开conf/web.xml配置文件,找到:
1 | <servlet> |
如果请求的资源是JSP页面,会使用JspServlet处理,将JSP页面转换为Servlet文件。
条