抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文介绍了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的目录结构:

code
1
2
3
4
5
6
7
8
apache-tomcat-x
├── bin
├── conf
├── lib
├── logs
├── temp
├── webapps
└── work

目录说明:

  • 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全生命周期,接收处理后的请求对象,处理会话信息,执行业务逻辑,返回响应数据。

模块划分:
20260106093417-总体

说明:

  • 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对象转为字节流,生成响应报文,发送给客户端。

连接器组成:
20260106130538-连接器

说明:

  • Endpoint:通信端点,具体的Socket接收处理类。
  • Processor:协议处理接口,构造Request对象和Response对象。
  • ProtocolHandler:协议处理器,封装了Endpoint组件和Processor组件,实现具体的协议处理逻辑。
  • Adapter:适配器,将Request对象转为ServletRequest对象,将ServletResponse对象转为Response对象。

3.2.2 容器

容器中存在多个层次:
20260106132510-容器

说明:

  • Engine:引擎,用于管理多个Host虚拟主机。一个Service组件包含一个Engine组件。
  • Host:虚拟主机,设置站点相关信息。一个Engine组件包含多个Host组件。
  • Context:上下文,为Wrapper组件提供运行环境。一个Host组件包含多个Context组件。
  • Wrapper:包装器,用于管理Servlet生命周期。一个Context组件包含多个Wrapper组件。

4 流程

4.1 启动脚本

通过启动脚本启动Tomcat服务。

脚本调用Bootstrap类的main()方法,这是启动的入口:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
// org.apache.catalina.startup.Bootstrap
public static void main(String args[]) {
// 创建Bootstrap对象
Bootstrap bootstrap = new Bootstrap();
// 创建类加载器
bootstrap.init();
// 设置daemon对象
daemon = bootstrap;
// 加载配置,逐级初始化
daemon.load(args);
// 逐级启动
daemon.start();
}

4.2 创建类加载器

调用Bootstrap的init()方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void init() throws Exception {
// 初始化类加载器
initClassLoaders();
// 设置当前线程上下文类加载器
Thread.currentThread().setContextClassLoader(catalinaLoader);
// 安全类加载
SecurityClassLoad.securityClassLoad(catalinaLoader);
// 通过反射创建Catalina对象
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// 反射设置父类加载器
Method method = startupInstance.getClass().getMethod("setParentClassLoader", new Class[]{Class.forName("java.lang.ClassLoader")});
method.invoke(startupInstance, new Object[]{sharedLoader});
// 设置Catalina对象
catalinaDaemon = startupInstance;
}

调用Bootstrap的initClassLoaders()方法:

java
1
2
3
4
5
6
7
8
private void initClassLoaders() {
// 创建common类加载器
commonLoader = createClassLoader("common", null);
// 创建server类加载器
catalinaLoader = createClassLoader("server", commonLoader);
// 创建shared类加载器
sharedLoader = createClassLoader("shared", commonLoader);
}

4.3 破坏双亲委派机制

双亲委派机制是Java类加载器的一种机制,用于保证Java核心库的安全性。

简单来说,当一个类加载器需要加载某个类时,它会先去自己的类路径中查找,如果没有找到,就去父类加载器的类路径中查找,以此类推,直到找到为止。

但是在某些情况下,开发者需要破坏这种机制,实现自定义的类加载逻辑。

如果Tomcat使用双亲委派机制来加载类,在多个Web应用中部署相同的类,但版本不同时,就会因为版本不同而导致问题。

为了解决多个Web应用的部署问题,在Tomcat中就破坏了双亲委派机制,目的是为了提高灵活性与隔离性:

  • 应用隔离:确保不同的Web应用互不影响,不会因为版本问题导致冲突。
  • 资源共享:让服务器的核心类库和Web应用的通用类库可以安全共享,避免重复加载。
  • 容器隔离:确保核心类库和Web应用的类库隔离,保证容器安全稳定。
  • 热部署:在不重启服务器的情况下,丢弃旧的类加载器,然后创建新的类加载器重新加载应用,从而实现热部署,这是双亲委派机制做不到的。

Tomcat的类加载机制如下:
20260106170303-连接器

说明:

  • 启动类加载器:用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()方法:

java
1
2
3
4
5
private void load(String[] arguments) throws Exception {
// 反射调用
Method method = catalinaDaemon.getClass().getMethod("load", null);
method.invoke(catalinaDaemon, param);
}

调用Catalina的load()方法:

java
1
2
3
4
5
6
7
8
9
10
public void load() {
// 加载并解析conf/server.xml配置文件
file = configFile();
// 设置Catalina对象
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// 执行初始化
getServer().init();
}

调用StandardServer的init()方法,实际调用LifecycleBase的init()方法:

java
1
2
3
4
5
6
7
8
public final synchronized void init() throws LifecycleException {
// 设置状态并触发INITIALIZING事件的监听器
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 抽象模板方法,子类负责具体实现
initInternal();
// 设置状态并触发INITIALIZED事件的监听器
setStateInternal(LifecycleState.INITIALIZED, null, false);
}

调用StandardServer的initInternal()方法,初始化Service组件:

java
1
2
3
4
5
6
protected void initInternal() throws LifecycleException {
// 初始化Service组件
for(int i = 0; i < services.length; ++i) {
services[i].init();
}
}

调用StandardService的initInternal()方法,初始化其他组件:

java
1
2
3
4
5
6
7
8
9
10
protected void initInternal() throws LifecycleException {
// 初始化Engine组件
engine.init();
// 初始化Executor组件
executor.init();
// 初始化MapperListener组件
mapperListener.init();
// 初始化Connector组件
connector.init();
}

调用Connector的initInternal()方法,初始化ProtocolHandler组件:

java
1
2
3
4
5
6
7
protected void initInternal() throws LifecycleException {
// 设置Adapter适配器
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// 初始化ProtocolHandler组件
protocolHandler.init();
}

调用AbstractProtocol的init()方法,初始化Endpoint组件:

java
1
2
3
4
5
6
7
public void init() throws Exception {
// 设置Endpoint组件
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length() - 1));
endpoint.setDomain(domain);
endpoint.init();
}

调用AbstractEndpoint的init()方法:

java
1
2
3
4
5
6
7
public void init() throws Exception {
// 创建并初始化Socket对象
if (bindOnInit) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
}

4.5 逐级启动

调用Bootstrap的start()方法:

java
1
2
3
4
5
public void start() throws Exception {
// 反射调用
Method method = catalinaDaemon.getClass().getMethod("start", null);
method.invoke(catalinaDaemon, null);
}

调用Catalina的start()方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void start() {
try {
// 执行启动
getServer().start();
} catch (LifecycleException e) {
// 执行销毁
getServer().destroy();
return;
}
// 注册关闭钩子
if (useShutdownHook) {
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
// 等待关闭
if (await) {
await();
stop();
}
}

调用StandardServer的start()方法,实际调用LifecycleBase的start()方法:

java
1
2
3
4
5
6
public final synchronized void start() throws LifecycleException {
// 设置状态并触发STARTING_PREP事件的监听器
setStateInternal(LifecycleState.STARTING_PREP, null, false);
// 抽象模板方法,子类负责具体实现
startInternal();
}

调用StandardServer的startInternal()方法,启动Service组件:

java
1
2
3
4
5
6
protected void startInternal() throws LifecycleException {
// 启动Service组件
for(int i = 0; i < services.length; ++i) {
services[i].start();
}
}

调用StandardService的startInternal()方法,启动其他组件:

java
1
2
3
4
5
6
7
8
9
10
protected void startInternal() throws LifecycleException {
// 启动Engine组件
engine.start();
// 启动Executor组件
executor.start();
// 启动MapperListener组件
mapperListener.start();
// 启动Connector组件
connector.start();
}

调用Connector的startInternal()方法,启动ProtocolHandler组件:

java
1
2
3
4
5
6
protected void startInternal() throws LifecycleException {
// 设置状态
setState(LifecycleState.STARTING);
// 启动ProtocolHandler组件
protocolHandler.start();
}

调用AbstractProtocol的start()方法,启动Endpoint组件:

java
1
2
3
4
5
6
7
8
public void start() throws Exception {
// 启动Endpoint组件
endpoint.start();
// 启动线程
timeoutThread.setPriority(priority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}

调用AbstractEndpoint的start()方法:

java
1
2
3
4
5
6
7
8
9
public final void start() throws Exception {
// 创建并初始化Socket对象
if (bindState == AbstractEndpoint.BindState.UNBOUND) {
bind();
bindState = AbstractEndpoint.BindState.BOUND_ON_START;
}
// 启动线程
startInternal();
}

调用NioEndpoint的startInternal()方法:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void startInternal() throws Exception {
// 创建ThreadPoolExecutor对象
createExecutor();
// 创建并启动Poller线程
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
// 创建并启动Acceptor线程,监听请求
startAcceptorThreads();
}

5 配置

5.1 修改端口

进入解压目录,打开conf/server.xml文件,找到:

server.xml
1
2
3
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="80000"
redirectPort="8443"/>

修改port属性为新的端口号。

5.2 命令行乱码

进入解压目录,打开conf/logging.properties文件,找到:

logging.properties
1
java.util.logging.ConsoleHandler.encoding = UTF-8

修改encoding属性为GBK后,保存并重启。

5.3 修改管理密码

进入解压目录,打开conf/tomcat-users.xml文件,在tomcat-users标签中增加:

tomcat-users.xml
1
2
<role rolename="manager-gui"/>
<user username="用户" password="密码" roles="manager-gui"/>

保存并重启,通过用户和密码访问管理页面。

5.4 默认映射

5.4.1 处理静态资源

进入解压目录,打开conf/web.xml配置文件,找到:

web.xml
1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<!-- 省略其他配置 -->
</servlet>
<!-- 省略其他配置 -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

如果没有配置处理静态资源的Servlet映射,会使用DefaultServlet在Web应用的目录结构中查找对应的静态资源文件。

5.4.2 处理JSP页面

进入解压目录,打开conf/web.xml配置文件,找到:

web.xml
1
2
3
4
5
6
7
8
9
10
11
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<!-- 省略其他配置 -->
</servlet>
<!-- 省略其他配置 -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>

如果请求的资源是JSP页面,会使用JspServlet处理,将JSP页面转换为Servlet文件。

评论