Tomcat实战指南:从安装部署、war包配置到JVM调优与HTTPS双向认证
2026/9/16 5:34:23 网站建设 项目流程

还在调Tomcat?启动闪退、部署war包404、改配置改到怀疑人生……这些坑我基本都踩过一轮了。这篇文章把Tomcat从安装选型、IDE配置、war包部署,到server.xml核心参数、内存调优、常见故障排查,再到稍微进阶一点的双向HTTPS认证实验,一次讲清楚。里面所有步骤都是我实际验证过的,可以直接照着做。

1. Tomcat到底是干嘛的,为什么Java Web离不开它

1.1 从一个Servlet请求说起

很多刚接触Java Web的同学,把Tomcat当成一个简单的"启动就能跑"的工具,但完全不理解它做了什么。我用一个最直白的场景来解释:你写了一个Servlet,里面有个doGet方法,你想让用户在浏览器输入http://localhost:8080/hello就能看到响应。问题是,你写的Servlet是纯Java类,它自己不监听端口,不解析HTTP协议,也不知道怎么把响应写回浏览器。谁来做这些事?就是Tomcat。

Tomcat本质上是一个Servlet容器 + HTTP服务器。它启动后监听8080端口(默认),把收到的HTTP请求解析成HttpServletRequest对象,然后根据请求路径找到对应的Servlet,调用它的service方法,再把HttpServletResponse里的内容封装成HTTP响应写回给浏览器。整个过程里,"解析协议""管理生命周期""线程分配"这些脏活累活全是Tomcat干的,你只需要关心业务逻辑。

这也是为什么Spring Boot出现后,很多人觉得"没有Tomcat也能跑Web应用"——其实不是没Tomcat,而是Spring Boot把Tomcat内嵌进了工程,你启动main方法时它自动帮你把内嵌Tomcat拉起来了。这个区别很关键,后面我会专门讲。

1.2 Tomcat源码与目录结构的对应关系

拿一个标准的Tomcat 9解压包来看,目录大概是这样的:

目录作用
bin存放启动与关闭脚本,如startup.sh、shutdown.sh、catalina.sh
conf全局配置文件,重点是server.xml、web.xml、context.xml
libTomcat依赖的jar包,比如servlet-api.jar
logs运行日志,默认有catalina.out、localhost_access_log等
temp临时文件目录
webapps放置web应用的位置,war包或解压后的目录都放这里
workJSP翻译成Servlet后的Java源码与class文件存放位置

work目录很多人不知道,但对排查JSP问题极有用。JSP第一次被访问时,Tomcat会把它翻译成一个Servlet的Java文件,然后编译成class。翻译出来的Java文件就放在work/Catalina/localhost/你的应用名/org/apache/jsp/下面。如果你怀疑JSP里写的Java代码有语法问题,或者想确认Tomcat到底把JSP编译成什么样,直接去这个目录看源码就行。

1.3 选对版本比会配置更重要

Tomcat版本和Java版本是强绑定的,版本不匹配会直接启动失败或者报UnsupportedClassVersionError。我在实际工作中见过太多人拿Tomcat 10去跑Java 8的老项目,结果Servlet类都找不到,因为Tomcat 10开始用的是Jakarta EE命名空间(javax.servlet变成了jakarta.servlet),旧代码直接编译不过。

版本选型上我的建议是:

  • 如果你在学传统的Servlet/JSP,Java 8 + Tomcat 9是最稳的组合,网上资料多,坑基本都被踩平了。
  • 如果你用Java 11及以上的新特性,考虑Tomcat 10.1,但注意项目依赖的Servlet版本也要跟上。
  • Spring Boot项目别手动装Tomcat,直接用内嵌的,版本由Spring Boot父依赖统一管理。
  • 别用Tomcat 10以下的版本配Java 17,JSP编译时会出现奇怪的反射报错。

下载的话,认准Apache官网的Tomcat发布页面,Windows选zip包,Linux选tar.gz包,不要下源码包(Source Code Distribution)去自己编译,那是自己给自己找事。

2. Tomcat安装与配置,从Windows到Linux一次说清

2.1 环境准备:先确认JDK装好没有

Tomcat本身是用Java写的,运行它必须有JDK或JRE。装好之后先验证:

java -version

如果提示找不到命令,说明JDK没装好或者环境变量JAVA_HOME没配。Windows用户在系统环境变量里新建JAVA_HOME,指向JDK的实际安装目录,比如C:\Program Files\Java\jdk1.8.0_202,然后再在Path里加上%JAVA_HOME%\bin。Linux用户用export设置或者写入/etc/profile,格式类似。

安装Tomcat其实没有"安装"这个动作,下载对应系统版本的压缩包,解压到一个没有空格的路径(Windows尤其是,路径有空格会坑死你),就算"安装"完成了。比如Windows就解压到D:\apache-tomcat-9.0.85,Linux就解压到/opt/tomcat

2.2 Windows下启动与停止的正确姿势

bin目录下能看到startup.bat和shutdown.bat。直接双击startup.bat,会弹出一个命令行窗口,如果窗口一闪而过,大概率是JAVA_HOME没配好。遇到这种情况不要慌,不双击startup.bat,改成在命令行里进到bin目录,手动执行catalina.bat run,这样Tomcat会以前台模式运行,所有启动日志直接打在当前窗口,报什么错一目了然。

启动成功的话,控制台会输出类似这样的内容:

信息 [main] org.apache.catalina.startup.Catalina.start Server startup in [1234] milliseconds

然后浏览器访问http://localhost:8080,能看到默认首页。如果8080端口被占用,启动日志里会报java.net.BindException: Address already in use,这时要么换端口,要么找出占用进程。

停止服务用shutdown.bat即可。注意shutdown是依靠8005端口发送SHUTDOWN命令实现的,后面我把server.xml的时候会说。

2.3 Linux服务器上部署war包的标准流程

Linux上最常干的一件事情,就是把打好的war包扔到Tomcat里跑起来。完整流程是:

# 1. 解压Tomcat tar -zxvf apache-tomcat-9.0.85.tar.gz mv apache-tomcat-9.0.85 /usr/local/tomcat # 2. 把war包放到webapps目录 cp myproject.war /usr/local/tomcat/webapps/ # 3. 后台启动 /usr/local/tomcat/bin/startup.sh # 4. 看日志 tail -f /usr/local/tomcat/logs/catalina.out

这里有一个很容易犯的错:Tomcat默认在conf/server.xml里配置了autoDeploy="true",也就是说你把war包扔进webapps目录,它会自动解压并部署。但是如果你之前已经用shutdown.sh停掉了Tomcat,再复制war包,然后又没有清空work目录,启动后访问新版本页面发现还是老内容——这不是没部署,是旧版本的class文件还留在work目录里。规范做法是:先停Tomcat,删掉webapps下旧的解压目录和war包,删掉work目录下的对应缓存,再放新war包,最后启动。

排查进程用的是:

ps -ef | grep tomcat

或者更精确一些,用ps -ef | grep java看Java进程的启动参数。如果启动后访问不了,优先看logs/catalina.out,日志比任何猜测都靠谱。

2.4 验证一轮手动的完整部署

我建议每个初学者都手动做一遍"从下载到部署war包"的全过程。可以自己打一个最简单的war:

  1. 在任意目录建一个文件夹叫demo,里面建WEB-INF/web.xmlindex.jsp
  2. index.jsp里随便写一行<h1>Hello Tomcat</h1>
  3. jar -cvf demo.war *打包(在demo目录内执行),或者直接用IDEA的Build Artifact。
  4. 把demo.war复制到webapps,启动Tomcat。
  5. 访问http://localhost:8080/demo/

这一步做完,你对整个Tomcat的工作原理就会有一个直观的感知:原来war包放到webapps下,Tomcat会自动解压、编译JSP、启动应用。之后再去调配置、弄双向认证,就有了底层基础。

3. IDEA、Eclipse集成Tomcat,以及war包部署方式的选择

3.1 IDEA 2025版本配置Tomcat详细步骤

IDEA配置Tomcat,分"配置Tomcat服务器"和"配置部署方式"两步。先说配置服务器:

打开IDEA,进入File -> Settings -> Build, Execution, Deployment -> Application Servers,点加号选Tomcat Server,然后在Tomcat Home中选择你的Tomcat解压目录。IDEA会自动识别版本号,填好Tomcat Base Directory(一般和Home一致),点OK保存。这一步如果识别不到版本,基本是JDK没配对,IDEA会在下方提示,按提示把Project SDK设成对应版本的JDK。

然后是部署Web项目。这里要区分情况:

  • 传统Web项目(没有用Maven骨架创建的标准模板,或者老式Java EE项目):打开Project Structure -> Artifacts,点加号添加Web Application Exploded,设置好Output directory,IDEA会把你项目里的JSP、class、resources打成一个可运行结构。
  • Maven项目:直接用插件方式运行,或者同样在Artifacts里配置war exploded。

接着在Run -> Edit Configurations里点加号选Tomcat Server -> Local。切到Deployment标签页,点加号选Artifact,把刚才配置的war exploded选中。Application context可以设置成/demo之类的路径,这个决定了访问时的根路径。最后启动,浏览器自动打开http://localhost:8080/你的context

3.2 为什么推荐Deployment用Exploded而不是war包

IDEA里部署时你会看到两个选择:demo:wardemo:war exploded,很多人不理解差别。

  • demo:war是完整war包,IDEA会先构建war,再复制到Tomcat的webapps目录,Tomcat再解压。每次改代码都要重新打包,开发效率低。
  • demo:war exploded是直接把编译后的目录结构交给Tomcat,相当于IDEA帮你维护了一个"已经解压好的应用"目录。配合IDEA的Update resources和热部署按钮,改个JSP、改个静态资源,点击更新就能立即生效,不用重启服务。

我的开发习惯是:日常开发一律用exploded模式,发布到测试机或生产环境才用完整war包。前者是为了开发效率,后者是为了环境一致。

3.3 Eclipse配置Tomcat和IDEA有什么不同

Eclipse用的是"Server视图"模式,流程上略有差异。先在Window -> Preferences -> Server -> Runtime Environments里Add一个Tomcat版本,指定安装目录,然后到Servers视图里新建一个Server实例。

Eclipse有个比较坑的地方:默认Server Locations是"Use workspace metadata",也就是它不会用你解压出来的Tomcat的webapps目录,而是在workspace里建一份副本。有时候你在tomcat/webapps下放了外部war包,发现没生效,原因就在这。建议在Servers视图双击Server,打开配置面板,把Server Locations改成Use Tomcat installation,并且把Deploy Path设置成webapps。这样行为和手动部署就一致了。

另一个常见坑是Eclipse部署后访问http://localhost:8080/项目名出现404,但IDEA里同样的项目没问题。多半是Eclipse的Context Path和artifact名字不一致,在Server配置的Modules标签页里检查一下Path列,改成实际想要的访问路径。

3.4 部署后去哪儿看JSP编译出的Java类

不管用哪种IDE,部署成功之后,你都可以去Tomcat的work目录看JSP底部。比如应用名是demo,访问过一次index.jsp后,去work/Catalina/localhost/demo/org/apache/jsp/目录,会看到index_jsp.javaindex_jsp.class两个文件。

打开index_jsp.java,里面是Tomcat生成的Servlet代码。你能清楚地看到:JSP里的HTML内容被写进了out.write()方法里,JSP里的表达式<%= ... %>被写进了out.print()调用里,你声明的局部变量变成了_jspService方法内的局部变量。这个文件在看JSP编译报错时尤其有用,Tomcat日志里报的行号经常是编译后Java文件的行号,而不是JSP的行号,对着这个文件更快定位。

4. server.xml核心配置与JVM调优参数实践

4.1 从一段实际配置认识server.xml的骨架

Tomcat全局配置的核心在conf/server.xml。打开一个默认的配置文件,你会看到这样的结构:

<Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> </Host> </Engine> </Service> </Server>

理解这个结构有个生活化类比:Server是整栋楼,Service是楼里的营业模块,Connector是接待前台,Engine是业务调度室,Host是楼层,Context是房间。请求从Connector进来,经过Engine分配,到Host下的某个Context(也就是具体某个Web应用)处理。

几个关键属性逐一说:

  • port="8005" shutdown="SHUTDOWN":这是Tomcat用来接收关闭命令的端口。执行shutdown.sh就是往这个端口发一个SHUTDOWN字符串。这个端口只监听本机,不需要暴露到外网,最好别改,除非和别的程序冲突。
  • port="8080":HTTP访问端口,要开外网访问的话记得在防火墙放行。
  • redirectPort="8443":当请求要求HTTPS但当前Connector是HTTP时,Tomcat会把请求重定向到8443端口。这个属性在配置HTTPS后特别容易踩坑,后面实验部分会具体说。
  • protocol="HTTP/1.1":默认的NIO连接器。不要改成HTTP/1.0,性能差别很大。
  • connectionTimeout="20000":接收请求后,等待参数的毫秒数,默认20秒。

4.2 Host、Context与虚拟主机配置

Host标签的name="localhost"表示域名。你想用自定义域名访问,比如www.mysite.com,就在这里加一个Host节点,或者把localhost改成你的域名。appBase="webapps"表示应用的根目录是webapps。

Context有两种配置方式:一种是在Host标签内显式写:

<Context path="/demo" docBase="D:/projects/demo" reloadable="true" />

另一种是在conf/Catalina/localhost/下放一个demo.xml文件,文件名就是path。第二种方式在生产环境更推荐,改配置不用动主文件。

docBase可以指向Tomcat外部目录,这个很实用。比如你不想把项目文件放到Tomcat的webapps里,就能用docBase="/data/apps/myapp"来指定。reloadable="true"开启后,class或配置文件发生变化会自动重新加载,开发方便,生产环境建议改成false,省去不必要的类加载开销。

4.3 JVM内存参数怎么调才不OOM

很多人问"Tomcat启动报java.lang.OutOfMemoryError: PermGen space或者Java heap space怎么办",这其实不是Tomcat的问题,是JVM的内存参数设置问题。Tomcat启动脚本里默认的JVM堆内存很小,并发稍高就爆了。

修改方法是编辑bin/catalina.sh(Linux/Mac)或bin/catalina.bat(Windows),在文件开头加上:

JAVA_OPTS="-Xms512m -Xmx1024m -XX:MaxPermSize=256m"

参数含义:

参数作用
-Xms512mJVM初始堆内存大小
-Xmx1024mJVM最大堆内存大小,生产环境至少2g以上
-XX:MaxPermSize老版本的永久代内存大小,Java 8之后改成-XX:MaxMetaspaceSize
-XX:+UseG1GC用G1垃圾回收器,Java 8更新版后建议加上

JDK1.8之后不用设置PermSize,Metaspace默认是无限的,但保险起见可以设置-XX:MaxMetaspaceSize=256m防止极端情况内存泄漏。

有一个误区必须指出:堆内存不是越大越好,-Xmx设置太大反而会导致GC停顿时间变长。一般的原则是堆最大值不超过物理内存的50%,比如8G内存的服务器设置-Xmx4g左右,再配合-Xmn设置新生代大小。

改完之后重启Tomcat,效果立竿见影。怎么看当前JVM实际参数?用jps查看进程号,再用jinfo -flags 进程号就能看到全部启动参数。

4.4 并发连接参数到底怎么配

默认情况下,Tomcat的HTTP连接器是单线程的吗?不是。NIO连接器内部有一个线程池,默认maxThreads是200。这意味着同一个时刻最多有200个请求在并行处理,超过的请求会在acceptCount队列里排队。acceptCount默认100,排队的连接超过这个数会被拒绝。

生产环境可以根据机器配置适当调大:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="400" maxConnections="1000" acceptCount="200" minSpareThreads="50" />

maxThreads不是无脑调大。每个线程占用一定的内存和CPU资源,线程数过多,上下文切换反而拖慢性能。我的建议是:CPU核心数乘以25左右作为初始值,跑压力测试,观察响应时间和CPU占用再微调。minSpareThreads意思是启动时预创建多少空闲线程,设成50左右可以加速请求响应。

5. 高频报错排查:从启动失败到404的完整方案

5.1 启动一闪而过怎么定位

Windows下双击startup.bat,窗口一闪就没,是最常见的问题之一。原因可能有三个:

  1. JAVA_HOME未配置或路径错误。
  2. 8080端口被其他程序占用。
  3. bin目录下的catalina脚本权限或编码问题。

定位方法很简单:在命令行里执行catalina.bat run,Tomcat会以前台模式启动,原来一闪而过的错误信息会完全显示出来。如果是JAVA_HOME配置错误,它会在开头直接报找不到java.exe;如果是端口占用,会报java.net.BindException。看到具体报错再对症下药。

有个冷门坑:有的人从云盘下载的所谓"Tomcat集成环境",bin目录里有些bat文件被Windows Defender或者安全软件拦截,执行到一半被kill,表现就是"窗口闪一下就没了"。解决方法:解压路径不要放在桌面或下载目录,放在一个干净的目录,比如D:\dev\,然后确认脚本没被杀毒软件隔离。

5.2 启动成功但访问404

启动日志显示Server startup in xxx milliseconds,但浏览器访问http://localhost:8080出现404,或者是访问自己部署的项目时404。分情况排查:

  • 如果是访问根路径404,说明ROOT应用没生效。检查webapps下有没有ROOT文件夹,如果误删了,Tomcat能启动但首页确实不存在。
  • 如果是访问http://localhost:8080/demo/404,先看webapps下有没有对应的demo目录或demo.war。war包在部署时会自动解压,如果没解压,说明解压失败,去logs里看catalina.out的详细报错。
  • 如果确认war包已解压、目录都存在,那要看logs/localhost_access_log.*.txt。这个文件记录了所有的HTTP访问日志,状态码404会记录在最后几位。同时看localhost.*.log日志,那是具体某个应用启动时的日志。

还有一个隐蔽问题:你在IDEA里设置了Application context/,此时访问路径应该是http://localhost:8080/,而不是/demo。很多人在这里绕晕。

5.3 端口占用与8005奇怪的关闭行为

报错java.net.BindException: Address already in use: JVM_Bind <null>:8080,说明8080端口被其他进程占用了。Windows下用:

netstat -ano | findstr :8080

看到占用端口的PID之后,打开任务管理器,找到对应进程结束它。如果这个进程是之前没关干净的Tomcat,别直接结束进程,尝试执行一次shutdown.bat,让它走正常关闭流程。有时候Tomcat进程还活着,执行shutdown.bat却提示没有这个进程,检查一下conf/server.xml里8005端口是否被改过,shutdown="SHUTDOWN"的值是否被改过。我遇到过有人把shutdown属性改成了SHUTDOWN123,结果用默认的shutdown.bat永远关不掉服务。

5.4 war包部署失败的日志解读

war包部署常见的报错有这几种:

  • 解压失败,日志里出现Exception processing ... war,多半是war包损坏,用压缩软件重新打包,或者用jar -xvf检查完整性。
  • 启动时ClassNotFoundException或NoClassDefFoundError,说明war包WEB-INF/lib下缺少某个jar,或者jar版本冲突。检查一下你的项目依赖里的两个jar是否同时引入了同一个类的不同版本。
  • 应用启动成功,但访问时一直转圈,日志里出现大量org.apache.catalina.core.StandardWrapperValve的报错,这类一般是应用自身的异常,需要看localhost.log里完整的堆栈。

还有一种我现在都还会踩的坑:war包里的WEB-INF/web.xml写的版本号是3.1,但Tomcat版本是10.0(Servlet 5.0),编译器会报"web-app_3_1.xsd not found"或类似错误。这不是致命错误,但会引入奇怪的运行行为。最好的做法是web.xml的版本和容器版本保持一致。

5.5 高并发场景下的超时问题

实际部署到服务器后,偶尔会碰上"请求响应特别慢"或"偶尔超时"的情况。这时候先看connectionTimeout有没有被误调大。我见过有人为了"防止超时",把connectionTimeout调到60万毫秒的,结果就是有个连接卡住不放,线程池很快被占满,所有请求看起来都变慢了。

正确的思路是分层排查:

  • 网络层:用curl -w查看各自耗时,确定是建立连接慢还是响应慢。
  • 应用层:看catalina.out有没有长时间GC停顿,用jstat -gcutil 进程号查看GC使用率。
  • 容器层:检查线程池是否被打满。jstack导出线程快照,看看大量线程阻塞在哪里。

遇到"偶发超时"不要猜,拿线程快照和GC数据说话,大部分问题都能当场定位。

6. 实验:把Tomcat配成双向HTTPS客户端,实现mTLS认证

6.1 这个实验解决什么问题

搜索热词里有"tomcat做为客户端,请求服务端,实现mtls双向认证配置",这个属于有点进阶的内容,但实际项目里确实会用到。它解决的是这样一个问题:你的Java后端服务A要调用另一个服务B的HTTPS接口,而且B要求客户端出示证书(mTLS双向认证)。你不想写独立的Java客户端代码,而是想让服务A本身(跑在Tomcat里)能带着证书去请求B。

换句话说,这里的关键不是Tomcat自己作为被访问的服务器,而是要回答"如何在Tomcat环境里配置一个可以发起HTTPS请求、并携带客户端证书的HTTP客户端"。下面用Java原生API实现,不需要引入额外框架,如果你项目里是Spring的RestTemplate或者HttpClient,原理一样,只是封装了底层。

6.2 用keytool生成服务端和客户端证书

mTLS需要两对密钥:服务端一对,客户端一对。先建目录存证书,我放在/tmp/mtls,实际项目请放在受保护的目录。执行:

# 服务端密钥库 keytool -genkeypair -alias serverkey -keyalg RSA -keysize 2048 \ -validity 365 -keystore server.keystore -storepass server123 \ -dname "CN=localhost, OU=Dev, O=Example, L=City, ST=State, C=CN" # 客户端密钥库 keytool -genkeypair -alias clientkey -keyalg RSA -keysize 2048 \ -validity 365 -keystore client.keystore -storepass client123 \ -dname "CN=client, OU=Dev, O=Example, L=City, ST=State, C=CN" # 导出证书并互相导入信任库 keytool -export -alias serverkey -keystore server.keystore -storepass server123 \ -file server.cer keytool -export -alias clientkey -keystore client.keystore -storepass client123 \ -file client.cer keytool -import -alias servercert -file server.cer -keystore client.truststore \ -storepass client123 -noprompt keytool -import -alias clientcert -file client.cer -keystore server.truststore \ -storepass server123 -noprompt

-dname里的CN=localhost很重要,如果服务端地址是IP或域名,这里要和访问地址匹配,否则握手时报证书主机名不匹配。

6.3 Tomcat服务端启用HTTPS并开启客户端证书校验

如果Tomcat是服务端,需要在server.xml里新增或修改一个Connector。默认配置里8080那个Connector有redirectPort="8443",这个8443就是给HTTPS预留的。增加一个专门的双向认证Connector:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreFile="conf/server.keystore" keystorePass="server123" truststoreFile="conf/server.truststore" truststorePass="server123" />

注意几个属性的作用:

  • clientAuth="true"表示强制要求客户端出示证书,改成want表示请求客户端证书但客户端不提供也能建立连接。
  • keystoreFile是服务端私钥所在的密钥库,用于向客户端证明自己身份。
  • truststoreFile是信任库,注意它和keystoreFile必须分开,因为它存放的是被信任的客户端证书。

配置完成后,重启Tomcat,浏览器访问https://localhost:8443,会出现客户端证书选择框(如果你已把客户端证书导入浏览器),这就是双向认证正在工作的表现。

6.4 纯Java客户端发起mTLS请求

现在写一个最直观的Java客户端,代码里嵌入了加载密钥库的逻辑:

import javax.net.ssl.*; import java.io.*; import java.net.URL; import java.security.KeyStore; public class MtlsClient { public static void main(String[] args) throws Exception { // 加载客户端密钥库 KeyStore clientKs = KeyStore.getInstance("JKS"); try (InputStream ksIn = new FileInputStream("client.keystore")) { clientKs.load(ksIn, "client123".toCharArray()); } KeyManagerFactory kmf = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm()); kmf.init(clientKs, "client123".toCharArray()); // 加载信任库 KeyStore trustKs = KeyStore.getInstance("JKS"); try (InputStream tsIn = new FileInputStream("client.truststore")) { trustKs.load(tsIn, "client123".toCharArray()); } TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustKs); SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); URL url = new URL("https://localhost:8443/"); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); int code = conn.getResponseCode(); System.out.println("HTTP状态码: " + code); try (BufferedReader br = new BufferedReader( new InputStreamReader(conn.getInputStream()))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } } }

这一段代码的核心逻辑是KeyManagerFactory负责提供客户端证书,TrustManagerFactory负责校验服务端证书。两个角色不能混。实际项目里如果服务端只要求单向HTTPS,那么客户端只需要配置TrustManager,不需要KeyManager。

如果连接报SSLHandshakeException,99%是证书链不完整或信任库没有正确导入对方证书。用keytool -list -keystore查看信任库内容,确认证书已经导入。

6.5 在Tomcat应用里封装一个可复用的HTTPS客户端工具

前面是独立Java程序,实际是要在Tomcat里跑的Web应用内调用别人的接口。可以把上面逻辑封装成一个工具类,每次请求时复用SSLContext,避免每次new一个,否则高并发下性能很差。

public class MtlsHttpUtil { private static volatile SSLContext sslContext; public static SSLContext getSslContext() throws Exception { if (sslContext == null) { synchronized (MtlsHttpUtil.class) { if (sslContext == null) { // 加载客户端密钥库和信任库,逻辑同前 sslContext = buildSslContext(); } } } return sslContext; } }

然后在Servlet或业务代码中,用HttpsURLConnection或者把sslContext塞给HttpClient使用。重点提醒:生产环境别把keystore密码硬编码在代码里,从配置文件读取,且配置文件本身要有访问控制。

7. 从传统Tomcat到内嵌Tomcat,再到其他Web容器

7.1 Spring Boot里的Tomcat到底做了什么

Spring Boot项目里没有单独的Tomcat安装目录,但运行时你依然会在日志里看到Tomcat的启动信息,这是因为spring-boot-starter-web默认引入了spring-boot-starter-tomcat,应用启动时在内存中创建了一个内嵌Tomcat实例。这种模式下,你不需要管理server.xml、不用去webapps下放war包,只需要在application.yml里配置:

server: port: 8080 servlet: context-path: /demo tomcat: max-threads: 200 accept-count: 100

Spring Boot还允许你替换掉Tomcat,改成Undertow或Jetty。只需要在Maven里排除Tomcat:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency>

至于引入具体容器后怎么配线程池和SSL,各自有独立的配置前缀。这里不细说,但思路一样:先搞懂Web容器通用概念,再迁移到具体产品上会快得多。

7.2 老项目迁移到其他Web容器时要注意什么

一个传统Web项目如果想从一个Web容器迁移到另一个Web容器,比如想把Tomcat换成其他主流中间件,需要注意三点。

第一,Servlet API版本。项目里web.xml的version和依赖的javax.servlet版本决定了容器的最低要求,容器低于这个版本会启动报错。第二,JSP标签库依赖。项目里引了哪些JSTL实现jar,不同容器自带的库可能冲突,迁移后出现NoClassDefFoundError就先清理WEB-INF/lib里已有的servlet-api和jsp-api相关的jar,这些容器自带,不要打包进去。第三,部署结构差异。有的容器解压war的路径、自动部署机制和日志目录不太一样,命令行参数和JMX端口基本不通用。

7.3 容器选型的个人建议

传统Servlet/JSP教学和老系统维护,Tomcat 9是最省心的;新项目如果不想被容器管理拖累,Spring Boot内嵌Tomcat最顺手;想极致压缩内存、提高吞吐,可以考虑Undertow,它的内存占用比Tomcat低不少;至于国内一些金融或政务项目要求用国产中间件替换,这类一般就是运维指定的,开发侧改动不大,核心思路是理解它们只是Web容器的不同实现,部署方式类似。

8. 再补几个我自己常用的排查和调优小技巧

最后分享几个翻来覆去用得上的小技巧,不算系统知识,但关键时刻能救你一把。

catalina.out文件越来越大,磁盘被塞满,这在长线运行的服务器上几乎是必然遇到的事。别等爆了再处理,提前配置logrotate或者自己写定时任务按大小切割。实在不行,重启时把catalina.out清空也行,> /usr/local/tomcat/logs/catalina.out,这不影响运行,只是清空文件内容。

排查进程用的是:

ps -ef | grep tomcat

但有时候进程存在,端口也监听正常,却怎么都访问不了。别忘了看防火墙。Linux下:

firewall-cmd --list-ports firewall-cmd --add-port=8080/tcp --permanent firewall-cmd --reload

很多部署问题不是Tomcat的问题,是防火墙把端口挡了。

还有一个跟路径有关的坑:生产环境Tomcat的webapps目录权限不能是777,也不能用root用户跑Tomcat。普通用户启动、目录属主设为该用户,避免安全问题。很多中间件漏洞都是因为权限太宽被利用的。

对于JSP改动不生效的怪问题,如果确认reloadable是true还是不行,直接停掉Tomcat,删除work目录下对应应用的缓存,再启动。这个操作我写过太多遍,每次都有效——大部分"改了没反应"的情况都是work缓存和classloader在搞鬼。

最后是一条真实经验:改任何Tomcat配置之前,先把原始文件备份一份,cp server.xml server.xml.bak。别嫌啰嗦,我见过N次改着改着回不去了、又忘了原配置长什么样的窘境。备份文件,用完就删,不占地方,但能救命。

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

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

立即咨询