资讯动态

Windows内网Tomcat 10.0.13解压部署与Jakarta迁移

发布时间:2026/10/23 9:50:30 来源:尧图企业网站定制
简介Apache Tomcat 10.0.13 Windows 64 位官方版面向 Java Web 开发者、运维人员及 Servlet/JSP 学习者提供轻量级应用服务器发行包便于搭建本地运行环境、部署测试 Web 应用并了解 Tomcat 的核心组件与目录组织。压缩包共 644 个文件约 12.58MB以 html 文档、class 与 java 源码示例、jsp 页面、jar 库为主并含 xml/properties 配置、bat/sh 脚本、图片与说明文件覆盖 bin、conf、lib、logs、webapps、work 等典型结构。已有 278 人浏览学习。其价值在于保留官方发行包的原始文件组成便于读者查阅 Catalina、Jasper、Coyote 相关资料结合配置文件和示例应用理解服务注册、WAR 部署、安全配置与性能调优思路也适合作为课程实验、项目联调和版本对照的参考包。1. 一台没有外网的 Windows 机器和一份解压即用的 apache tomcat 10.0.13接手内网交付的活最怕对面丢一句「装个 Tomcat 就行」。机器上没外网、没管理员密码、JDK 还是半年前别人随手装的这时候 MSI 安装包反而成了负担写注册表、抢端口、卸载还留一堆尾巴。apache tomcat 的 windows-x64 压缩包正好相反解压、设两个环境变量、跑 startup.bat一个能接 WAR 的 Servlet 容器就立在 D 盘了要挪走删目录就行。但 10.0.13 卡在一个特殊位置——它实现的是 Jakarta EE 9lib 里只有 jakarta.* 的 API拿它接老项目会当场翻车。这篇按我实际部署的顺序把这份包从解压、Connector 参数、服务注册到 javax 迁移和排错拆一遍适合要在 Windows 上快速起一个容器的人。2. 解压到跑起来路径、环境变量与第一个 WAR这一章的目标很单一从拿到压缩包到浏览器里看到你自己的页面。步骤不多但 Windows 上几个老毛病路径带空格、JRE_HOME 抢优先级、双击闪退会让人卡半小时以上。2.1 解压路径与环境变量的硬约束先说路径。不要解压到C:\Program Files\下面一是路径里有空格某些脚本拼接 classpath 时会断二是这个目录受 UAC 保护服务方式启动时权限容易出问题。我一般固定放D:\server\下目录名保持压缩包原名不加中文、不加版本后缀说明REM 解压后的目录形如 D:\server\apache-tomcat-10.0.13 REM 先确认 bin 目录下同时存在 startup.bat 与 tomcat10.exe dir /b D:\server\apache-tomcat-10.0.13\bintomcat10.exe和tomcat10w.exe是这个 windows-x64 包特有的压缩包里带了 procrun 包装器后面注册 Windows 服务就靠它。如果你下载到的包里没有这两个 exe说明拿到的是通用 zip服务注册会走另一条路。环境变量只设一个JAVA_HOME指向 JDK 根目录不是 binsetx JAVA_HOME C:\Program Files\Java\jdk1.8.0_341 /M setx PATH %PATH%;%JAVA_HOME%\bin /M这里有个血泪经验Tomcat 的setclasspath.bat判断顺序是先看 JRE_HOME再看 JAVA_HOME。很多机器上装着老版本 JRE 并设了JRE_HOME结果启动日志里的 JVM 版本跟你以为的完全不一样跑起来报UnsupportedClassVersionError才知道用错了。要么把JRE_HOME删掉要么让它跟JAVA_HOME指向同一个 JDK。10.0.x 这条线最低要 Java 8装 JDK 8 或 11 都能跑。REM 验证输出的路径必须指向 JDK且位数与包匹配x64 包里是 64 位包装器 echo %JAVA_HOME% %JAVA_HOME%\bin\java -version2.2 七个目录各管什么解压完先别急着启动花两分钟认一下目录后面出问题能少走弯路目录作用能否清空常见误用bin启动脚本、service.bat、tomcat10.exe否把setenv.bat放到别处导致不生效confserver.xml、web.xml、context.xml、tomcat-users.xml否直接改web.xml全局配置升级被覆盖lib容器级 jar含jakarta.servlet-api等否往这里塞业务 jar和 webapp 类加载器打架logscatalina、localhost、localhost_access_log可出问题只看控制台不看这里的完整堆栈temp上传临时目录、文件锁停服后可服务运行中删导致上传报错webapps默认部署目录可生产环境留着manager、host-manager、docsworkJSP 编译产物停服后可改 JSP 不生效时忘了清这里work那一行值得单独说JSP 第一次访问会被编译成 Servlet 落在work\Catalina\localhost\应用名\下改完 JSP 没反应八成是时间戳没触发重编译停服删work目录再起就好。生产环境把manager、host-manager、docs、examples四个目录删掉这是最省事的一刀。2.3 启动与部署第一个 WAR启动有两种方式区别很大REM 方式一新开窗口启动窗口一闪而过时你看不到任何报错 cd /d D:\server\apache-tomcat-10.0.13\bin startup.bat REM 方式二前台运行日志直接打在當前窗口排错首选 cd /d D:\server\apache-tomcat-10.0.13\bin catalina.bat run新手最常踩的坑就是双击startup.bat窗口闪一下就没了然后说「启动不了」。它其实只是「启动失败并关闭了窗口」。用catalina.bat run前台跑异常堆栈会老老实实打出来。看到Server startup in [xxxx] milliseconds才算真正起来了。部署一个 WAR 就是把文件拷进webappsREM 拷贝即部署文件名决定访问路径demo-app.war 对应 /demo-app copy /Y D:\build\demo-app.war D:\server\apache-tomcat-10.0.13\webapps\ REM 想看部署过程盯 catalina 日志的最新一份 dir /o-d D:\server\apache-tomcat-10.0.13\logs\catalina.*.log这里有两个要点。第一Host的autoDeploy和unpackWARs默认都是true容器会自己解包、自己建 Context不需要你写任何配置第二文件名就是 context pathROOT.war才会映射到根路径/改名demo-app.war之后访问地址立刻变成/demo-app很多人改完名忘了这点回头访问 8080 根路径得 404。2.4 起没起来的三条验证别靠「浏览器没报错」判断按这个顺序确认REM 1. 端口在不在监听 netstat -ano | findstr :8080 REM 2. 容器自身是否响应 curl -I http://127.0.0.1:8080/ REM 3. 日志里有没有 SEVERE findstr /I SEVERE ERROR D:\server\apache-tomcat-10.0.13\logs\catalina.*.logcurl -I返回200或404都说明容器活着404 只是根应用不存在连不上才是容器没起来。第三步那行findstr最好养成习惯有些应用启动时报了SEVERE但容器照样对外服务等到第一次请求才炸。提示确认能用之后把conf\server.xml里port8005的 shutdown 端口和port8080都记下来同一台机器跑第二个实例时必须改否则第二个实例起不来还不报明显错误。3. server.xml 与 Connector把 8080 调成能扛事的样子出厂server.xml是给「跑通」用的不是给「跑稳」用的。这一章处理三个真问题Connector 参数怎么定、应用目录放哪、怎么变成开机自启的 Windows 服务。3.1 Connector 的六个必调参数先看默认 Connector 长什么样再逐项改!-- conf/server.xml 中 Service 节点内的 HTTP Connector -- Connector port8080 protocolorg.apache.coyote.http11.Http11NioProtocol address0.0.0.0 connectionTimeout20000 maxThreads400 minSpareThreads50 acceptCount200 maxConnections8192 URIEncodingUTF-8 compressionon compressionMinSize2048 compressibleMimeTypetext/html,text/xml,text/plain,text/css,application/json,application/javascript relaxedQueryChars[]|{}^#x5c;#x60;quot;lt;gt; redirectPort8443 /protocol显式写成Http11NioProtocol是为了避免不同 JDK 上自动选择带来的行为差异也让后来接手的人一眼看出用的是 NIO。connectionTimeout属性本身的默认值是 60000 毫秒出厂文件里写的是 20000内网应用 20 秒够用对外服务建议拉到 30000 以上否则慢接口会被容器直接掐断连接。maxThreads是最容易被拍脑袋设的参数。默认 200它对应的是并发正在处理的请求数不是连接数。经验值是按「单机 CPU 核数 × 25」估一个起点IO 密集的应用可以往上加纯计算型的加到 400 反而会因为线程切换变慢。acceptCount是队列长度线程打满后新连接堆在这里超过就拒绝内网应用 200 足够公网入口要跟前面的负载均衡策略对上。relaxedQueryChars这一项是真正的后悔药。Tomcat 从 8.0 开始严格校验请求行里的字符URL 里带|、{、}、[、]一律返回 400前端把 JSON 直接拼在 query string 里时必翻车。注意 XML 里不能写裸的、、要用字符引用上面那串#x5c;#x60;quot;lt;gt;就是反斜杠、反引号、双引号、尖括号的转义写法抄的时候别漏。3.2 应用目录放哪三种部署方式的选择WAR 丢webapps是开发期最省事的方式但升级 Tomcat 时整个目录要换应用配置会跟着丢。我一般用第三种方式把配置和容器解耦!-- conf/Catalina/localhost/demo-app.xml文件名即 context path -- ?xml version1.0 encodingUTF-8? Context docBaseD:/apps/demo-app reloadablefalse crossContextfalse Manager pathname / /ContextdocBase指向容器外的目录用正斜杠或转义反斜杠这样 Tomcat 升级只要换CATALINA_HOME业务文件原地不动。reloadablefalse是生产环境的必须项设成true时容器会不停扫描 class 变更CPU 白烧而且热加载常常只重载了一半的类引发各种ClassCastException。Manager pathname /关掉 Session 持久化到磁盘很多莫名其妙的「重启后拿到脏 Session」就是它引起的如果确实要重启保留会话就配PersistentManager和FileStore别用默认行为。还有一种是把META-INF/context.xml打进 WAR 里方便但耦合同一份 WAR 想在不同环境用不同数据源就难受了。选型上我的排序是外部 xml WAR 内置 直接改server.xml里的 Context。3.3 注册成 Windows 服务service.bat 的实际用法交付到客户现场没人愿意每次登录服务器手动跑 bat。这个包带service.bat一条命令注册成服务cd /d D:\server\apache-tomcat-10.0.13\bin REM 安装服务并指定初始堆注意服务名只能英文数字不要带空格 service.bat install Tomcat10Demo --Startup auto --JvmMs 2048 --JvmMx 4096 REM 启动并确认状态 net start Tomcat10Demo sc query Tomcat10Demo REM 改内存参数不用重装update 即可 service.bat update Tomcat10Demo --JvmMs 2048 --JvmMx 4096 REM 卸载 net stop Tomcat10Demo service.bat remove Tomcat10Demo几个必须知道的边界。第一服务注册时记录的是当前目录作为CATALINA_HOME所以一定要先cd到 bin 再执行否则服务指向一个不存在的路径启动时报错还很难查。第二服务默认以 LocalSystem 身份运行如果你的应用要访问网络共享目录或者别的用户配置下的文件权限会跟你在桌面上双击启动时完全不同这类「本地能跑、服务跑不了」的问题十有八九是这里。第三--JvmMx不要超过机器可用内存的六成设大了服务启动直接失败错误只写在logs\commons-daemon.*.log里事件查看器里看到的只是一个 1067。装好之后想改 JVM 参数除了service.bat update也可以直接运行tomcat10w.exe //ES//Tomcat10Demo打开图形配置界面Java 选项卡里改 Initial/Maximum memory pool。这个 exe 的//ES//参数后面必须跟准确的服务名名字写错就静默退出什么都不提示。3.4 setenv.batJVM 参数该写在哪不要直接改catalina.bat升级一次全丢。正确做法是在bin下新建setenv.bat容器启动时会自动调用echo off REM setenv.bat 放在 %CATALINA_BASE%\bin 下不是 CATALINA_HOME set CATALINA_OPTS-Xms2g -Xmx2g -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m set CATALINA_OPTS%CATALINA_OPTS% -Dfile.encodingUTF-8 -Duser.timezoneAsia/Shanghai set CATALINA_OPTS%CATALINA_OPTS% -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPathD:\logs\heap set JAVA_OPTS%JAVA_OPTS% -Djava.awt.headlesstrueJAVA_OPTS和CATALINA_OPTS的区别值得记牢JAVA_OPTS会传给version、stop这些命令CATALINA_OPTS只传给真正启动 Tomcat 的那个进程。所以内存参数、GC 参数放CATALINA_OPTS避免停服务的时候也去申请 2G 堆。-Xms和-Xmx设成一样大是为了避免运行期扩堆代价是启动时就把内存占住交付到内存紧张的机器上要往下调。HeapDumpOnOutOfMemoryError那个路径必须先建好目录容器不会替你创建OOM 时路径不存在堆转储就静默丢了这算是典型的「配了等于没配」。4. javax 到 jakarta10.0.13 最容易踩的迁移坑这一节是整份包里最需要提前知道的事。10.0.13 不是「9 的补丁版」它换掉了整个 API 命名空间。4.1 老 WAR 扔进去为什么直接报错现象很固定应用部署上去日志里出现java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet或者ClassNotFoundException: javax.servlet.Filter又或者更诡异的——类都加载成功了WebServlet注解标注的接口却 404。原因是 Tomcat 10.0 实现 Jakarta EE 9Servlet 规范从 4.0 升到 5.0包名整体从javax.servlet.*改成jakarta.servlet.*。容器的lib里放的是jakarta.servlet-api根本没有javax.servlet这个包。注解扫描也一样它扫的是jakarta.servlet.annotation.WebServlet你代码里写的是javax.servlet.annotation.WebServlet容器压根不认识这个注解于是接口注册不上部署日志里连个 WARN 都没有。注意这不是配置问题改任何server.xml都解决不了。要么把应用迁移到 jakarta要么换回 Tomcat 9 那条 javax 线二选一。选型上给个直白的判断Spring Boot 2.x 及更早打出来的 WAR直接上 Tomcat 9Spring Boot 3.x 或者自己维护 Servlet 代码的项目用 10.x。中间没有既省钱又省事的第三条路。4.2 迁移检查从依赖树到字节码迁移第一步不是改代码是先摸清有多少 javax 依赖混在里面REM 只筛 servlet / websocket / el 这三类输出重定向到文件方便比对 mvn -f D:\src\demo-app\pom.xml dependency:tree -Dincludesjavax.servlet:*,javax.websocket:*,javax.el:* D:\build\deps.txt findstr /I javax.servlet jakarta.servlet D:\build\deps.txt输出里但凡出现javax.servlet:javax.servlet-api、javax.servlet:jstl这类坐标就得换。换的时候注意传递依赖很多老框架自己不声明 servlet-api靠的就是容器提供你换成 jakarta 之后它的内部字节码还是 javax这时候只能升级框架版本。第二步改源码。接口方法签名不变变的只有包名// 迁移后的 Filter包名全部换成 jakarta方法签名与原来一致 import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; public class DemoFilter implements Filter { Override public void doFilter(jakarta.servlet.ServletRequest request, jakarta.servlet.ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException { // 业务逻辑完全不用动只有类型所在的包变了 chain.doFilter(request, response); } }全局替换时有个陷阱别把字符串常量里的javax.servlet一起替换掉。有些项目把类名写进了反射配置或者 XML替换错了要跑起来才发现。IDE 的「替换全部」务必限定在*.java的 import 与类型引用范围内替换完git diff扫一眼。第三步改描述符。web.xml的版本号和 schema 位置都要跟着升!-- Servlet 5.0 的命名空间注意是 jakarta.ee不是 java.sun.com -- web-app xmlnshttps://jakarta.ee/xml/ns/jakartaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttps://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd version5.0 display-namedemo-app/display-name /web-appJSP 那边同样要动JSTL 的 taglib uri 从http://java.sun.com/jsp/jstl/core改成jakarta.tags.core并且WEB-INF/lib下要放 JSTL 3.0 的实现用 1.2 的实现照样跑不起来。第四步改完别急着部署先用javap在字节码层面复核一遍这一步能提前拦住 90% 的漏网之鱼REM 解包 WAR 后用 javap 反查常量池里还有没有 javax/servlet 引用 cd /d D:\build\demo-app-unpacked\WEB-INF\classes javap -p -c com\example\demo\DemoServlet | findstr /I javax servlet输出为空才算干净。要批量查整个目录写个循环把WEB-INF\classes下所有 class 过一遍比人眼翻代码可靠得多。4.3 两个命名空间能不能共存这是被问得最多的问题能不能把javax.servlet-api.jar塞进WEB-INF/lib让老 WAR 直接跑在 10.0.13 上技术上类能加载但没用。Tomcat 内部是按jakarta.servlet.Servlet这个类型去识别和调用组件的你的 Servlet 实现的是javax.servlet.Servlet两者不是同一个类型容器在扫描阶段就把它当普通类跳过了不会注册路由不会调用init。所以你最多能做到「不报 ClassNotFound」但请求还是 404问题从显式报错变成了隐形失败更难查。真要少改代码有个折中思路是用字节码迁移工具批量把 class 里的javax/servlet常量池引用改成jakarta/servletWAR 解包、批量改、重新打包。这条路对纯 Servlet/JSP 项目有效对依赖了框架内部反射、动态代理、OSGi 的应用就不一定了框架内部可能硬编码了类名字符串改字节码改不到。所以我的建议是能升级依赖就升级不能升级就老老实实选 Tomcat 9别在中间地带耗时间。5. 排查清单五个把服务卡在启动阶段的坑下面五条是我在 Windows 上部署 Tomcat 时反复遇到的按「现象 → 原因 → 解决」记遇到同类问题可以直接对号。5.1 双击 startup.bat 窗口一闪而过现象双击startup.bat黑窗口闪一下就关闭浏览器访问 8080 无响应logs目录里也没新日志。原因startup.bat会用start新开一个窗口跑catalina.bat如果JAVA_HOME没设、指向 JRE、或者位数不匹配64 位包装器配 32 位 JVM子进程立刻退出父窗口也就跟着关了错误信息全丢在已经关闭的子窗口里。解决永远用catalina.bat run前台启动看第一手报错。如果是JRE_HOME干扰临时set JRE_HOME再启动验证如果是位数问题%JAVA_HOME%\bin\java -version输出里会带64-Bit字样对不上就换 JDK。5.2 8080 端口被占用或第二个实例起不来现象启动日志末尾报Address already in use: bind或者机器上跑第二个 Tomcat 时明明改了 8080 还是失败。原因除了 HTTP 的 8080server.xml里还有shutdown端口 8005 和 AJP 端口 8009三个都要改只改一个必然冲突。另外 Windows 上 8080 常被别的服务占着。解决先查占用再动手REM 查出占用 8080 的进程 PID确认是不是自己的旧实例 netstat -ano | findstr :8080 tasklist | findstr 上一步查到的PID REM 确认可以杀再杀别误杀别的服务 taskkill /PID PID /F多个实例共存时把shutdown端口改成 8006、8007 依次递增AJP 那段在 10.0.x 里默认是注释掉的不用管。5.3 中文乱码但只在某一种场景下乱现象控制台启动日志里中文变问号或者页面正常但 GET 参数乱码或者反过来页面乱码日志正常。原因三种乱码三个根因。控制台乱码是 cmd 的代码页默认 936和 JVM 的file.encoding不一致GET 参数乱码在 Tomcat 8 之后已经很少见因为URIEncoding默认就是 UTF-8问题通常出在前端没做encodeURIComponent或者网关转了一道响应乱码则是Content-Type里没带charsetUTF-8。解决setenv.bat里加-Dfile.encodingUTF-8cmd 里执行chcp 65001再看日志。响应侧统一在过滤器里设response.setCharacterEncoding(UTF-8)或者由框架的编码过滤器兜底。注意别在server.xml里再手动写URIEncodingISO-8859-1那是给老版本擦屁股的写法现在写上去只会制造新问题。5.4 请求返回 400日志说 Invalid character现象某些带参数的 URL 一访问就 400日志里是Invalid character found in the request target把参数删掉就正常。原因Tomcat 严格按 RFC 校验请求行|、{、}、[、]、反斜杠这些字符默认不允许出现在 URL 里。前端把 JSON、数组参数直接拼进 query string 时必中。解决在 Connector 上加relaxedQueryChars路径里也可能出现的话再加relaxedPathChars。写的时候注意 XML 转义、、都要用字符引用否则server.xml直接解析失败容器起不来报的还是一句很含糊的Error parsing server.xml。改完先跑catalina.bat run确认能起来再挂服务。5.5 服务装上了net start 报 1067 或 1053现象service.bat install提示成功sc query能看到服务但net start立刻失败事件查看器里只有「进程意外终止」。原因service.bat注册时记录的是执行命令那一刻的当前目录。如果你在别的目录执行或者先装服务再挪了 Tomcat 目录服务指向的路径就是错的。另一个常见原因是--JvmMx设得超过物理内存JVM 起不来服务管理器只报一个笼统的错误码。解决错误细节在logs\commons-daemon.YYYY-MM-DD.log里第一行通常就写着真正的失败原因。路径错就service.bat remove后cd到 bin 重装内存参数错就service.bat update往下调。顺手确认服务名没写错tomcat10w.exe //ES//服务名里的名字必须和sc query看到的完全一致。6. 进阶日志切分、访问日志与一份程序跑多实例容器能跑只是及格线交付到现场还得管两件事日志别把磁盘写爆以及一台机器上跑多个环境。Tomcat 的日志走 JULI配置在conf\logging.properties。出厂配置里catalina日志是按天滚动的但保留天数是 90 天且单个文件不限大小业务日志一多logs目录很快就上 G。我的习惯是把关键几行调成下面这样重点在maxDays和rotatable# conf/logging.properties 片段 # 1catalina.org.apache.juli.AsyncFileHandler.level FINE 1catalina.org.apache.juli.AsyncFileHandler.directory ${catalina.base}/logs 1catalina.org.apache.juli.AsyncFileHandler.prefix catalina. 1catalina.org.apache.juli.AsyncFileHandler.suffix .log 1catalina.org.apache.juli.AsyncFileHandler.maxDays 15 1catalina.org.apache.juli.AsyncFileHandler.rotatable true 1catalina.org.apache.juli.AsyncFileHandler.bufferSize 8192访问日志默认不开需要自己在Host里加 Valve。开了之后每一条请求一行排查慢请求、确认 caller IP 全靠它!-- conf/server.xml 的 Host 节点内rotatable 让文件按天切 -- Valve classNameorg.apache.catalina.valves.AccessLogValve directorylogs prefixaccess. suffix.log pattern%{yyyy-MM-dd HH:mm:ss}t %h %l %u quot;%rquot; %s %b %D rotatabletrue /pattern里的%D是处理耗时毫秒这个字段比什么都值钱接口慢不慢一眼就看出来。%h记客户端 IP前面挂了反向代理的话要换成%{X-Forwarded-For}i否则所有请求的 IP 都是代理机的。多实例的做法是拆CATALINA_BASECATALINA_HOME只留一份程序每个实例目录下复制conf、logs、temp、webapps、work五个目录然后写个启动脚本分别指定REM 实例一端口 8080指向自己的 base 目录 set CATALINA_HOMED:\server\apache-tomcat-10.0.13 set CATALINA_BASED:\server\instance-a call %CATALINA_HOME%\bin\catalina.bat runCATALINA_BASE下的bin\setenv.bat才是这个实例真正生效的那份很多人改了CATALINA_HOME\bin\setenv.bat发现不生效就是被 base 目录里的同名文件覆盖了。最后说个验证习惯。压测不用上大工具ab -n 2000 -c 100 http://127.0.0.1:8080/demo-app/api/ping打一轮同时开个窗口看jstack里线程池的状态http-nio-8080-exec-*线程数量一直贴着maxThreads上限说明该调线程或者查慢 SQL 了而不是继续往上加线程数。从那以后我每次落地一个新的 Tomcat 目录都会先用catalina.bat run前台跑一遍、看到Server startup in那行再挂服务配置错在启动阶段就暴露比事后翻日志省事得多。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑