☰
Tomcat 10.0.13 Windows部署:javax迁移到jakarta
2026/10/15 15:27:47 网站建设 项目流程

简介:Apache Tomcat 10.0.13 Windows x64官方版是一套面向Java Web开发者的开源应用服务器发行包,基于Jakarta Servlet与JSP规范构建,适合在64位Windows环境中部署和运行Web应用,也便于开发调试与教学实践。压缩包共644个文件,约12.58MB,内容涵盖229个HTML静态页、103个Class类文件、76个Java源码及58个JSP页面,同时包含配置XML、JAR库、批处理脚本、样式表与文档,基本覆盖服务器运行、部署和二次开发所需的完整组件,这些文件分别承担静态页面展示、Java类加载、JSP编译、配置解析、启动停止等职责,解压后可看到bin、conf、lib、logs、webapps等标准目录结构。启动脚本(如catalina.bat、version.bat等)可直接操作实例,lib与conf目录则对应依赖与配置调整,借助这些内容可梳理Tomcat的Catalina、Jasper、Coyote等核心模块,便于后续做端口调整、部署WAR包或排查启动故障。包内附带的文档与示例页面也便于查阅默认首页和配置说明。已有275人学习下载,适合初学者熟悉目录结构,也可供有经验者查阅官方发行版的组件组成与默认配置。

1. 打开 10.0.13 之前,先接受一个有点扎心的事实:它和 9.x 不是同一个世界

某开发者第一次把 Tomcat 9 上跑得好好的 war 包丢进 apache-tomcat-10.0.13-windows-x64 解压目录,启动没报错,请求一发就抛NoClassDefFoundError: javax/servlet/http/HttpServlet。这个版本不是 9.x 的例行升级,Servlet 规范从 4.0 跳到 5.0,连包名都从javax整个换成了jakarta。所有旧代码、旧依赖、旧教程里看到的import javax.servlet在这个版本上全部失效。它适合谁的话题很明确:想踩上 Jakarta EE 9 的新项目、准备从 9.0.x 迁移过来的应用维护者,以及必须在 Windows 64 位环境里拿到一个干净稳定运行包的人。本文不负责帮旧应用骗过这个版本,而是把官方版真正为哪些场景准备的、怎么落地、坑在哪,一条条讲清楚。

2. Windows x64 部署第一步:把 JDK 校验、解压目录和启动脚本一次理顺

这一章从拿到压缩包到浏览器出页面,按最不容易翻车的方式走一遍。先校验 JDK,再解压,最后用能真正看到日志的方式启动。

2.1 先确认机器上是 64 位 JDK,再谈 JAVA_HOME 和 PATH 的优先级

x64 位压缩包对应 64 位 Tomcat 核心,运行它必须有一个 64 位 JVM。先开一个 CMD 窗口执行:

java -version

输出里能看到64-Bit Server VM,说明当前 PATH 命中的就是 64 位 JDK。如果只有Server VM或者干脆是 32 位,Tomcat 虽然能勉强启动,但地址空间、线程栈、后续 JVM 参数上限全会被砍一刀。Tomcat 10.0.x 官方要求 Java 8 及以上,而 10 系是基于 Jakarta EE 9 做的实现,JDK 11 更贴近日常使用环境,我一般直接统一成 JDK 11,避免后面 JSP 编译和类元数据处理因为 JDK 过旧冒出奇怪问题。

JDK 装好后设置两个环境变量:

setx JAVA_HOME "C:\Program Files\Java\jdk-11" setx CATALINA_HOME "C:\tools\apache-tomcat-10.0.13"

这里有个 Windows 专属的坑:setx写入的是注册表,只对新开的终端生效,当前这个 CMD 窗口永远读不到新值。所以设置完必须新开窗口验证:

echo %JAVA_HOME%

如果输出的还是旧路径,后面startup.bat就极可能找到错误的java.exe。这是 Windows 上启动 Tomcat 最常见的第一道翻车点,和版本无关,但 10.0.13 的启动脚本对JAVA_HOME检查比旧版更严格,环境变量不对会直接拒绝启动。

外一个常见误区:不要为了省事把 JDK 的bin目录塞进 PATH 就以为万事大吉。Tomcat 的catalina.bat优先找JAVA_HOME,找不到才退回去用 PATH 里的 java。机器上同时装多个 JDK 时,PATH 顺序决定你用的是哪个,最好让JAVA_HOME和 PATH 指向同一个版本,避免启动日志里出现java.version老半天对不上的情况。

2.2 解压到不含空格的目录,把 bin conf webapps 的职责记牢

下载的apache-tomcat-10.0.13-windows-x64.zip解压后,整个目录就是 Tomcat 的全部。我建议放到一个不含中文、不含空格的路径,例如C:\tools\apache-tomcat-10.0.13。放在C:\Program Files下多数时候也能跑,但脚本引号处理、后续服务安装路径、目录权限都会复杂不少,不值当。

解压后的目录结构是这样:

C:\tools\apache-tomcat-10.0.13\ ├── bin\ 启动/关闭脚本、服务安装脚本、原生 exe 包装器 ├── conf\ server.xml、web.xml、context.xml、tomcat-users.xml ├── lib\ 容器自身的 jar 包 ├── logs\ catalina.*.log 是主日志 ├── temp\ 临时文件目录 ├── webapps\ 应用部署目录 └── work\ JSP 编译产物和临时类

重点记两个:conf是配置中心,work是动态编译产物目录。后面第 5 章的坑,几乎全落在conf和work的权限与内容上。

很多三方集成包会把目录改得乱七八糟,缺脚本、并目录、塞额外启动器。官方版的识别点就在bin目录:里面必须有完整的startup.bat、shutdown.bat、catalina.bat、service.bat,以及tomcat10.exe、tomcat10w.exe这两个 Windows 服务相关可执行文件。缺了service.bat,你后面就没法把 Tomcat 装成 Windows 服务。我拿到压缩包后还会顺手对一下官方提供的.sha512校验文件,这一步能避免拿到被替换过的二进制。

2.3 启动验证要留日志:startup.bat 看不到错,catalina.bat run 能看到

新开 CMD 执行:

cd /d C:\tools\apache-tomcat-10.0.13\bin startup.bat

startup.bat会弹出一个新窗口,主线程跑到新窗口里继续输出日志。问题是:如果 JVM 启动后三秒内退出,新窗口瞬间关闭,你什么都看不见,这就是 Windows 上经典的闪退黑匣子。

更稳的手法是用前台模式:

catalina.bat run

所有日志直接打在当前窗口,启动失败的真实原因一眼可见。启动成功后,浏览器访问http://localhost:8080/,能看到 Tomcat 默认主页。如果页面打不开,先确认 8080 端口有没有被别的进程占用:

netstat -ano | findstr :8080

-ano会列出 PID,拿到后在任务管理器里反查进程名。要知道,即使当前端口被占,startup.bat也已经把错误写进了logs\catalina.<日期>.log,闪退后打开这个文件同样能看到最后的堆栈。我个人的习惯是:第一次部署任何 Tomcat 实例,一律不用startup.bat,只用catalina.bat run,等确认稳定后才切换到服务模式。

3. 应用迁移的分水岭:javax 到 jakarta 的替换与旧应用共存方案

10.0.13 和 9.x 最本质的代差在包名。任何一个老应用部署上来,第一道坎一定是类加载失败的报错。搞清楚这道坎,你就同时理解了这版存在的意义和它不兼容旧应用的根源。

3.1 代码层最小改动:import 前缀从 javax 换成 jakarta

先看一段最典型的 Servlet 代码差异。Tomcat 9 时代,也就是 Servlet 4 规范时期的写法:

import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;

Tomcat 10.0.13 对应 Servlet 5 规范,写法变成了:

import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse;

从编译视角看,这就是 import 字符串替换。但影响远不止代码:项目里的 Maven 或 Gradle 坐标也要换。

<!-- 旧坐标(Servlet 4.0) --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- 新坐标(Servlet 5.0) --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>5.0.0</version> <scope>provided</scope> </dependency>

替换逻辑很简单:Tomcat 10 的lib目录里只有jakarta.servlet层的实现,classpath 里根本不存在javax.servlet类。即使你手动往lib里塞旧 jar,也会因为同名类冲突把别的应用一起带崩。这条路千万别走,我见过有人这么干,最后整个实例的应用全部启动失败。

3.2 依赖坐标、web.xml 与 JSP 引用的联动修改

代码之外,还有三个地方容易被忽略。第一是web.xml的命名空间头部:

<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd" version="5.0">

老项目的xmlns指向http://java.sun.com/xml/ns/javaee,在 Tomcat 10 上会告警,某些严格配置下解析也会出问题。别觉得这是无关痛痒的头部声明,容器读取web-app版本号,会影响后续对 Servlet 注解、默认 session 超时等行为的解释。

第二是 JSP 文件里的<%@ page import %>和taglib指令,同样要把包名换掉。JSP 编译阶段由 Jasper 完成,它拿到的类路径里只有 jakarta 实现,任何写死 javax 的 JSP 都会在第一次访问时报编译错误。

第三是反射字符串和 XML 里写死的类名。有些框架把javax.servlet.http.HttpServlet写进配置,或通过Class.forName动态加载,这种字符串替换不能用 IDE 的重构工具自动完成,得靠全文本搜索。迁移工具能做的也只是扫描,不能替你解逻辑。

3.3 旧 war 包不改代码的过渡:迁移工具说明与双实例共存

Apache 官方提供了一个迁移辅助工具,可以扫描 war 包或源码中的javax引用并给出替换方案。它不是一键神器。扫描结果里javax.servlet相关可以直接改,但框架内部的间接依赖、JSP 里行内类名、反射字符串里的类路径,都必须人眼二次确认。我一般流程是:先跑扫描器拿到清单,再改依赖和源码,最后在本地用 10.0.13 实例跑一遍核心业务流程做回归。

如果你维护的是一个历史包袱重、暂时又没资源全面重写的服务,我常用的中间方案是:Windows 机器上同时跑两个 Tomcat,10.0.13 承接新模块,旧 9.0.x 继续承担已有 war 包,两个实例用不同端口区分。这个方案成本最低,也不阻塞新项目采用 10 系。数据迁移和框架升级可以在两套实例之间慢慢并行,等新模块稳定后再逐步把旧流量切过来。

4. server.xml、setenv.bat 与 Windows 服务:把 10.0.13 调成能生产的样子

到这一步,Tomcat 已经能跑起来。但生产环境要的是三件事:连接器参数合理、JVM 内存受控、Windows 服务正常托管。这三件事任何一件没做好,服务器跑一个月后都会以某种形式还给你。

4.1 HTTP 连接器参数:线程数、排队数和压缩怎么给一个初始值

conf\server.xml里的 HTTP Connector 是请求入口。默认配置能跑 demo,但遇到流量波动时会暴露短板。我先给一份可以当起点的配置:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" acceptCount="200" maxThreads="300" minSpareThreads="20" maxKeepAliveRequests="100" compression="on" compressionMinSize="2048" compressibleMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/json"/>

几个关键参数对照如下:

参数默认值作用建议
maxThreads200最大工作线程数,决定并发上限按 CPU 核数乘以 50 起步,压测后再定
minSpareThreads10空闲时保持的最小线程数20 到 50,减少突发请求时建线程的开销
acceptCount100等待队列长度,满后拒绝新连接200 左右,和 maxThreads 一起压测
connectionTimeout20000等待请求行的超时时间,毫秒网络差的环境放宽到 30000,别超过 60000
compressionoff对响应做 gzip 压缩CPU 有余量就开 on,文本类接口收益明显

maxThreads不是越大越好。线程开到 500,数据库连接池却只有 20,最后瓶颈在下游;无压测依据就把值写高,系统反而会在线程切换上烧 CPU。我习惯先给一个保守值,压测时观察线程利用率曲线再上调。acceptCount容易被忽略,它的作用类似排队缓冲区,调太小会让连接直接被内核拒绝,客户端得到 connection reset;调太大又可能让请求在队列里等太久,体验比直接拒绝更糟。

4.2 堆内存参数与 UTF-8:新建 setenv.bat 而不是乱改 catalina.bat

很多人会直接去改catalina.bat里的JAVA_OPTS,这个文件是启动脚本模板,每次升级版本就可能被覆盖。正确做法是在bin下新建setenv.bat,Tomcat 启动时如果检测到它,会自动加载:

set "JAVA_OPTS=%JAVA_OPTS% -Xms1g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8"

-Xms和-Xmx分别是最小堆和最大堆。Windows 服务模式下,最大堆建议不要超过物理内存的一半,留空间给操作系统缓存、连接池和 JVM 自身其他区域。MetaspaceSize是类元数据区,JSP 编译产物和框架的类都放在这里,默认值偏小会导致频繁 Full GC。关键是那个-Dfile.encoding=UTF-8,Windows 中文环境默认字符集是 GBK,不显式指定的话,日志里的中文和 JSP 输出会乱码,尤其在使用 FileReader 之类不指定编码的代码时。

如果你用的是 JDK 11,可以加上 G1 垃圾回收器:

set "JAVA_OPTS=%JAVA_OPTS% -Xms1g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -Dfile.encoding=UTF-8"

改完setenv.bat,先停服务再启动验证,不要在旧进程还占着端口时直接重启,否则你分不清新的内存参数到底有没有生效。还有个细节:setenv.bat的文件编码必须是 ANSI 或无 BOM 的 UTF-8,用记事本另存时选错了编码会导致脚本解析出错。

4.3 安装成 Windows 服务并设置开机自启:service.bat 的正确用法

生产机器上不可能每次开机都手动开 CMD 启动。在bin目录执行:

cd /d C:\tools\apache-tomcat-10.0.13\bin service.bat install Tomcat10

成功后继续用命令行把启动类型设为自动:

sc config Tomcat10 start= auto sc start Tomcat10

注意sc config的语法里start=后面必须跟一个空格,写成start=auto会直接报参数错误。卸载服务则用:

service.bat remove Tomcat10

服务安装的原理是service.bat调用了 bin 下的tomcat10.exe。它以 Windows 服务的方式启动,不依赖某个用户登录,而且会把 CATALINA_HOME 和 JAVA_HOME 固化到服务配置里。服务名不要带空格,后续日志定位、权限设置都省事。

服务装好后,切忌再用startup.bat去启动。我吃过一次亏:一个实例已经以服务方式跑着,我没注意又执行了startup.bat,结果两个进程抢同一个 8080 端口,日志里出现Address already in use,排查了半天才意识到服务没停。判断 Tomcat 是否已作为服务运行,直接看任务管理器里有没有Tomcat10.exe进程,比盲猜端口更直接。

5. 10.0.13 Windows 部署避坑:五个最容易踩的启动与权限坑

部署次数多了,会发现坑就集中在几类。整理五个频率最高的,按现象、原因、解决的顺序写。

5.1 部署旧 war 包后 NoClassDefFoundError: javax.servlet

现象:war 包部署成功,访问任意 Servlet 立刻报NoClassDefFoundError: javax/servlet/http/HttpServlet,Tomcat 主页却正常。

原因:应用代码或依赖还停留在 javax 命名空间。Tomcat 10 的lib里没有这些类,它提供的是 jakarta。更隐蔽的是,有些 war 包的WEB-INF/lib里自带旧版 servlet-api,导致错误信息时有时无,干扰排查方向。

解决:先用压缩工具打开 war 包查看WEB-INF/lib,把里边的 servlet-api jar 移除,这种嵌入式 jar 和容器自带的实现叠加几乎必然冲突。然后按第 3 章的方式替换源码依赖。如果项目一时改不动,留在 Tomcat 9 是更诚实的做法,不要硬迁。

5.2 startup.bat 一闪而过,日志什么都没留下

现象:双击startup.bat,窗口闪一下就没了,8080 没起来,logs 目录里也无从下手,犹如一个纯黑匣子。

原因:JVM 启动后立即退出,错误只打印在闪掉的窗口里。绝大多数情况是JAVA_HOME没配对,或者 PATH 命中了另一个 Java 版本;也有少量情况是 CATALINA_HOME 指到了不存在的目录。

解决:不要双击,在 CMD 里跳到 bin 目录执行catalina.bat run,错误会停在当前窗口。最常见的报错是The CATALINA_HOME environment variable is not defined correctly,这种几乎全是环境变量路径或变量名错误。改完环境变量后开新终端验证echo %JAVA_HOME%,确认后再启动。这段黑匣子经历之后,我再没双击过startup.bat。

5.3 换端口没换全:8009 和 8005 也要照顾到

现象:启动失败,日志提示Address already in use: JVM_Bind,于是把 Connector 的 8080 改成 8081,结果还是起不来。

原因:Tomcat 的server.xml里不只有 HTTP Connector。AJP 连接器默认监听 8009,关闭监听的 Server 端口默认是 8005。这三个端口任何一个被占,Tomcat 都会拒绝启动,只改 8080 等于没改。

解决:先查全三个端口:

netstat -ano | findstr :8080 netstat -ano | findstr :8009 netstat -ano | findstr :8005

如果根本用不到 AJP,直接注释掉<Connector port="8009" protocol="AJP/1.3">那一行,减少一个隐患。改端口时三个地方同步改,并确认防火墙规则没有把新端口挡住。Windows 防火墙对 8080 往往已有放行规则,换成 8081 后别忘了新增规则。

5.4 Manager App 403:先补 tomcat-users.xml 角色,再谈权限

现象:http://localhost:8080/首页正常,点开 Manager App 却出现 403 Access Denied。

原因:官方压缩包的tomcat-users.xml是空壳,没有任何角色和用户。403 不是 Windows 文件权限问题,是 Tomcat 自身管理接口拒绝匿名访问。

解决:编辑conf\tomcat-users.xml,添加角色和用户:

<tomcat-users> <role rolename="manager-gui"/> <user username="admin" password="换成强密码" roles="manager-gui"/> </tomcat-users>

修改后重启 Tomcat。只为了用 web 管理界面,给manager-gui就够了,不要顺手把manager-script、admin-gui全配上,攻击面会变大。这条很容易和“改了 server.xml 里的 manager 路径”混淆,前者是用户授权,后者是路径映射,先查 tomcat-users.xml 再看别的地方。

5.5 服务模式下 JSP 报 500:work 目录没有写权限

现象:Tomcat 装成 Windows 服务后,第一次访问 JSP 页面报 500,日志里出现无法创建work/Catalina/localhost/...目录或 Permission denied。

原因:服务默认以系统账户运行,但你的 Tomcat 目录在 C 盘某深层路径下,服务账户没有写权限。JSP 第一次访问需要把源码编译到work目录,这一步失败就直接 500,代码本身其实没问题。

解决:给服务运行账户授予整个 Tomcat 目录的修改权限,或者做更规范的拆分——把 CATALINA_BASE 指到独立数据目录,比如D:\appdata\tomcat10,让webapps、work、logs和程序目录分离。升级时只替换程序目录,数据目录保留,既解决权限问题又方便回滚。这个结构是我在大型部署里比较依赖的布局,能少踩很多权限相关的坑。

6. 上生产前的三道验证与一个值得长期保持的配置习惯

这类版本值不值得投入,光看功能清单没用。我给 10.0.13 上生产前的建议是把验证做成固定三步。

第一步,部署一个知道自己该长什么样的实验应用:一个 Servlet、一个 JSP、一个静态文件、一个 WebSocket 端点。Servlet 验证 jakarta 包,JSP 验证编译链路,静态文件验证连接器压缩,WebSocket 验证 10.0.13 的 WebSocket 实现。这四个全通,说明新应用在这台 Windows 机器上没有根本性障碍。

第二步,用现有压测脚本压一遍,别只测首页。重点观察线程数曲线和拒绝连接出现的时间。maxThreads和acceptCount是否合理,反映在日志里的All threads busy字样,而不是 CPU 单核占用率。如果没有压测工具,用jstat -gc <pid>观察 GC 频率也能侧面判断设置是否健康。

第三步,迁移过后不要立刻删旧环境。旧 Tomcat 9 实例在 Windows 服务里保留两周,等新实例扛过真实业务流量再摘除。这个习惯帮过我一次:新实例上有个定时任务因为时区配置异常半夜翻车,靠旧实例的日志对比才把问题定位到是代码里写死了TimeZone.getDefault(),而不是新环境本身的问题。如果早就把旧环境卸载了,这种对比连参照物都没有。

我自己的习惯是拿到任何新版本 Tomcat,第一件事不是改配置,而是把conf目录整个复制一份改名conf.orig。每次调 server.xml 都对着原始文件做 diff,清楚知道哪行是版本默认值、哪行被我改过。经历过一次升级时配置被覆盖的教训以后,这个动作我坚持了很多年。10.0.13 之后再升到 10.0.x 的后续补丁版本,diff 能直接告诉你默认值有没有变化,不用靠记忆。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询