
写这套教程的起因很简单不少刚接触 Java Web 的朋友卡在第一步就备受挫折——Tomcat 下载好了IDEA 也装好了可项目就是起不来Servlet 明明写了却总是 404。我做过几年一线开发也带过新人很确定这些问题的根子基本不在代码而在配置链路的某个细节上。所以这篇我会用 IDEA 2024 最新版把“从 0 到 1 部署 Tomcat 添加 Servlet”这条路完整走一遍涉及到版本选型、环境变量、IDEA 集成、项目创建、Servlet 编写和调试排坑照着做就行。1. 项目概述先搞清楚 Tomcat 和 Servlet 各管什么事1.1 Tomcat 和 Servlet 是什么它们是什么关系Tomcat 由 Apache 软件基金会维护是一个开源的 Servlet 容器和 Java Web 服务器。它可以独立运行接收 HTTP 请求并按规则返回响应是绝大多数 Java Web 初学者的第一个运行环境。Servlet 则是一段运行在服务器端的 Java 类专门用来处理客户端请求并生成动态响应。打个比方Tomcat 是前台的“接待员”负责收件、拆信封、按地址找人Servlet 是后场的“业务员”负责真正处理事情再把结果交给前台发回去。浏览器发来的每一个请求都由 Tomcat 解析成 HttpServletRequest找到对应的 Servlet调用它的方法然后拿到 HttpServletResponse 写回浏览器。这个分工决定了我们的学习路径先让前台开工Tomcat 跑起来再让业务员上岗编写并注册 Servlet。两者缺一不可很多人学 Servlet 时感觉听不懂说白了就是没搞明白自己的代码是怎么被容器“钓”出来的。1.2 IDEA 2024 环境下的整个部署链路IDEA 2024 对 Java Web 开发的支持相当成熟从工程创建、依赖管理、Tomcat 集成到热部署调试基本做到了图形化操作。但正因为 IDE 自动化程度高新人反而容易忽略背后的东西IDEA 只是帮你把部署包推送到 Tomcat 的 webapps 目录本质跟手动复制 war 包没有区别。整个链路是源码编译为 class 文件按 Java Web 规范打包成 war/exploded 结构Tomcat 启动时加载对应目录读取注解或 web.xml 完成 Servlet 注册最后对外提供 HTTP 服务。这篇教程会按这条链路逐步落地每一步都讲清“为什么这么配”。1.3 版本选型最容易被老教程坑的地方Tomcat 从 10.0 开始做了一个影响面极广的改动Java EE 时代的javax.servlet.*包名全面迁移为 Jakarta EE 时代的jakarta.servlet.*。也就是说网上大量老教程里import javax.servlet.http.HttpServlet的写法在 Tomcat 10 上面是编译不过的或者运行时报ClassNotFoundException。我这篇默认使用 Tomcat 10.1.x 版本Servlet 使用 Jakarta 命名空间因为这是目前最主流的新环境配置方式。如果你公司项目还在用 Tomcat 9对应使用javax.servlet即可流程基本一致只是要特别注意包名差异。选型时不要盲目下最新版本先确认你的 JDK 版本、现有依赖和你所参考的教程是否匹配。2. 环境准备JDK 与 Tomcat 的下载、安装和验证2.1 下载 Tomcatzip 版还是安装版去 Apache Tomcat 官网下载时Windows 下会看到 32-bit/64-bit Windows Service Installer 和 Core 压缩包zip两种主流选择。我建议下载 zip 版本因为它开箱即用、不污染系统服务列表卸载也方便只要删目录就行。安装版会把 Tomcat 注册成 Windows 服务虽然开机自启省事但对学习阶段反而是干扰。Tomcat 10.1 需要 JDK 11 及以上我用的是 JDK 17这是当前支持周期和生态都比较平衡的版本。下载完成后将压缩包解压到一个不含中文和空格的路径下例如D:\apache-tomcat-10.1.33。不要放在C:\Program Files这类带空格的路径否则后续脚本解析变量时极容易出莫名其妙的路径错误。2.2 目录结构快速熟悉解压后你会看到如下目录这里挑几个必须认识的bin 存放启动、关闭脚本startup.bat / shutdown.bat conf 核心配置文件目录server.xml、web.xml、tomcat-users.xml lib Tomcat 自身依赖的 jar 包 logs 运行日志目录排错最常看的地方 webapps 部署目录war 包或解压后的项目丢到这里就能被加载 work JSP 编译后的 class 文件临时目录刚开始不用逐个钻只要记住代码部署到 webapps日志看 logs端口和虚拟主机配置在 conf/server.xml。我见过有人为了“部署”到处找地方最后才发现 webapps 这层的作用这就是对目录结构不熟导致的。2.3 配置 CATALINA_HOME 环境变量Tomcat 本身不强制要求配置环境变量在 bin 目录下双击 startup.bat 也能启动。但为了让 IDEA 能准确识别 Tomcat也为了方便在任意目录通过命令行使用脚本建议还是配置一下新建系统变量变量名CATALINA_HOME变量值为你的 Tomcat 解压目录例如D:\apache-tomcat-10.1.33。在系统变量Path中添加%CATALINA_HOME%\bin。检查JAVA_HOME是否已正确设置指向 JDK 安装目录。因为 Tomcat 启动脚本需要找 java 命令找不到会直接报JAVA_HOME相关错误。配置完环境变量后命令行执行echo %CATALINA_HOME%能正确显示目录就说明变量生效了。注意修改环境变量后要重新打开命令行窗口或重启 IDEA否则新值不会加载。2.4 第一次启动验证启动前先确认 8080 端口有没有被占用netstat -ano | findstr 8080如果有结果说明端口被占可以后续改端口或者先排查占用程序。确认端口空闲后进入 bin 目录双击startup.bat看到类似这样的日志基本就成功了Server startup in [1234] milliseconds然后浏览器访问http://localhost:8080看到 Tomcat 首页就算环境通了。如果控制台输出乱码大部分是编码问题可以在 log 输出的 cmd 窗口执行chcp 65001临时切到 UTF-8 编码或者在 conf/logging.properties 里调整字符编码。这一步卡住的人很多但基本都不涉及代码问题多查端口和 JAVA_HOME 就能解决。3. IDEA 2024 集成 Tomcat创建项目与运行配置3.1 版本区别Community 与 Ultimate 的关键差异IDEA 分为 Community社区版免费和 Ultimate旗舰版付费两个版本。社区版不是不能做 Web 开发而是缺少对“应用服务器”的图形化集成支持IDEA Ultimate 中可以直接在 Settings 里配置 Tomcat Application Server而社区版没有这个面板。如果你用的社区版有两种选择一是自己手动完成编译、打包、复制到 webapps 的流程这对理解底层机制很有帮助但不方便二是使用 Ultimate这是大多数团队的实际选择。下面操作均以 IDEA 2024 Ultimate 为准你打开 Settings 后如果找不到 Application Servers 选项就要先确认是不是版本问题。3.2 让 IDEA 识别 TomcatApplication Servers 配置打开 IDEA进入菜单File Settings Build, Execution, Deployment Application Servers点击加号选择 Tomcat Server在Tomcat Home处选择你本地的 Tomcat 解压目录。IDEA 会自动识别版本并提示缺失依赖一般直接 OK 即可。这一步不需要手动填太多东西但有个小细节复制 Tomcat 目录路径时一定要选到那一层apache-tomcat-10.1.x文件夹不要多选到 bin 目录也不要少选到外层某个父目录。IDEA 要从该目录下找 lib/catalina.jar 等核心文件路径错了表面看不出来运行时会报各种找不到类的错误。3.3 创建 Maven Web 项目既然要写 Servlet理论上手工创建普通 Java 项目再引入 servlet-api 也行但日常开发中 Maven 几乎成了标配所以我直接用 Maven 工程来讲也顺便解决依赖管理问题。新项目流程File New Project选择 Maven不选自带骨架。设置好 GroupId、ArtifactId 后在项目里手工补出 Web 目录结构。标准 Java Web 目录长这样src/main/java Java 源码 src/main/resources 资源文件配置文件、日志配置等 src/main/webapp Web 根目录 src/main/webapp/WEB-INF/web.xml 部署描述文件可选在pom.xml里添加 Servlet API 依赖。Tomcat 10.1 对应 Jakarta Servlet 5.0/6.0 规范我这里以 6.0 为例dependencies dependency groupIdjakarta.servlet/groupId artifactIdjakarta.servlet-api/artifactId version6.0.0/version scopeprovided/scope /dependency /dependenciesscope设置成provided很关键意思是编译和测试时用这个 jar但打包到 war 时不要包含进去因为 Tomcat 自己已经有实现了。如果漏了这点或者误设成 compile部署后虽然不至于立刻报错但会导致 jar 包冲突特别是以后引入 spring、拦截器过滤器时容易出问题。3.4 配置本地 Tomcat 运行任务工程搭好后再配置运行任务。点击右上角运行配置下拉框选择Edit Configurations新增一个Tomcat Server Local。在Deployment标签页点击加号添加Artifact选择项目的war exploded展开的 war 包。这里解释一下为什么优先选war exploded它不压缩直接以文件夹形式加载到 Tomcat 的 webapps 里IDEA 可以直接关联源码改代码后热更新速度更快。普通war适合交付生产使用本地开发选 exploded 体验更好。配置项里还有一个Application context默认是/项目名这个就是你的 Web 应用访问根路径。例如这里填/demo那么访问 Servlet 的 URL 就是http://localhost:8080/demo/hello。这个值记牢很多 404 就是因为它没找对。配置完成后点击运行按钮IDEA 会自动启动 Tomcat 并把工程部署进去。看到类似下面的日志就说明 IDE 集成没问题Artifact demo:war exploded: Artifact is being deployed Deployment of web application archive [demo] has finished4. 核心环节两种方式添加 Servlet附完整代码4.1 用注解配置 Servlet推荐方式Servlet 3.0 以后支持注解不用在 web.xml 里写任何内容直接在类上标注WebServlet即可。这种方式代码聚拢、可读性好我现在开发时默认都用注解。第一步在src/main/java下创建包com.demo.servlet新建类HelloServlet继承HttpServletpackage com.demo.servlet; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; WebServlet(/hello) public class HelloServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType(text/html;charsetUTF-8); resp.getWriter().write(h1Hello Servlet, IDEA 2024/h1); } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }第二步直接重新部署运行 TomcatIDEA 中按ShiftF10或用右上角运行按钮浏览器访问http://localhost:8080/demo/hello如果能看到Hello Servlet字样说明注解生效了Servlet 已经被 Tomcat 加载并注册。注意WebServlet(/hello)里的路径必须带斜杠而且不要写WebServlet(hello)这种缺斜杠的形式否则映射不生效。第三步解释几个容易踩的坑doGet和doPost要分开写还是可以合并看业务场景。表单 GET 请求通常走 doGetPOST 表单走 doPost。上面的写法是偷懒把 POST 也丢给 doGet真实项目中最好分别处理。继承的extends HttpServlet别写串。Tomcat 10 下一定要 importjakarta.servlet.http.HttpServlet不是javax。resp.getWriter()获取的是字符输出流要给浏览器返回中文时一定要先设置字符编码这就是为什么我在第一行写了setContentType(text/html;charsetUTF-8)。不加的话中文大概率变成乱码。4.2 用 web.xml 配置 Servlet传统方式注解虽然好用但有些老项目、部分中间件或前置过滤器场景还是要靠 web.xml。另外很多面试题和工作中的老代码还在用传统方式所以这块也必须掌握。项目没有 web.xml 时先手动创建src/main/webapp/WEB-INF/web.xml内容如下?xml version1.0 encodingUTF-8? 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/display-name !-- 声明 Servlet 类 -- servlet servlet-nameHelloServlet/servlet-name servlet-classcom.demo.servlet.HelloServlet/servlet-class /servlet !-- 配置 URL 映射 -- servlet-mapping servlet-nameHelloServlet/servlet-name url-pattern/hello/url-pattern /servlet-mapping /web-app注意区分servlet-name在两个标签里的作用它只是一个逻辑名称作用是把“类定义”和“URL 映射”连接起来。你完全可以随便起名但最好跟类名保持一致省得自己看晕。使用传统方式时上一个示例里的WebServlet注解必须删掉否则同一个 Servlet 被注册两次Tomcat 启动时会直接报重复映射的异常。servlet-class必须写全限定类名比如com.demo.servlet.HelloServlet不能只写HelloServlet。4.3 注解与 web.xml 的选择建议从 Servlet 3.0 到现在的 Jakarta EE注解已经是绝对主流它最大的优势是消除了大量“连接”代码。web.xml 仍然存在的意义在于没有源码的第三方 Servlet 也可以配置、某些 Servlet 在框架中需要按指定顺序实例化、部分运维场景想在不改代码的前提下调整映射。一般现代项目能用注解就绝不开 web.xml但理解两种方式背后的机制对读懂像 Spring MVC 的 DispatcherServlet 注册逻辑很有帮助。5. 部署运行与问题排查从启动报错到 404 定位5.1 部署后到底发生了什么当你点击 IDEA 的 Run 按钮IDEA 会执行这样一串动作把工程编译成 class将 webapp 资源和编译产出组织成 exploded war 结构复制到当前 Tomcat 实例关联的部署位置然后启动 Tomcat。Tomcat 启动时扫描对应应用的注解或者读取 WEB-INF/web.xml把 Servlet 注册进上下文容器。所以部署报错时先判断是哪个环节有问题。如果 IDEA 控制台只报“端口占用”那是启动前检查失败如果报“Artifact 部署失败”多半是构建目录不完整如果 Tomcat 正常启动但访问 404基本就是 Servlet 映射错误或 context path 不对。把问题定位到阶段排查效率会高很多。5.2 常见问题速查表下面这张表是我在实际带人过程中反复用到的排查清单覆盖新手最常遇到的场景现象可能原因处理方法启动时报Address already in use: JVM_Bind8080 端口被占用换端口或杀掉占用进程server.xml修改端口IDEA 中也要同步更新 HTTP port访问报 404Tomcat 首页能开Servlet 映射路径不对或 context path 不对确认WebServlet/url-pattern核对 Application context 值例如/demo/hello中的/demo来自部署配置启动报ClassNotFoundException: javax.servlet...用错命名空间使用了 Tomcat 9 的老代码将javax替换为jakarta或改用 Tomcat 9启动报重复映射注解和 web.xml 同时注册了同一个 Servlet二选一保留一种注册方式页面中文乱码没有设置编码或文件本身编码不对resp.setContentType(text/html;charsetUTF-8)IDEA 中 File Encoding 统一为 UTF-8getWriter()报 IOException已有其他输出流或响应已提交不要在一次性响应中重复调用检查是否先调用了getOutputStream()IDEA 控制台输出乱码控制台默认编码不是 UTF-8VM options 加-Dfile.encodingUTF-8或在Help Edit Custom VM Options中显式设置无法修改 Tomcat 端口改了server.xml但 IDEA 配置未同步IDEA 运行配置中单独维护 HTTP Port 设置要和 server.xml 保持一致5.3 搞定一堆奇怪报错后的调试技巧解决语法和配置问题之后真正写业务时最大的需求是调试。IDEA 里打断点调试 Servlet跟调普通 Java 程序差不多在代码行号左侧单击设置断点然后点击调试按钮虫形图标启动 Tomcat。当浏览器请求打到对应 Servlet 时IDE 会停留在断点处可以查看 HttpServletRequest 的请求参数、Header 等。用这种方式能直接看到请求进来了没有、走到了哪个方法、参数是什么。很多新手写 Servlet 一旦 404 就手足无措其实只要在doGet第一行打个断点然后刷新浏览器如果断点没有命中说明映射就没进来问题在 URL 或部署地址如果断点命中说明映射没问题是业务逻辑或响应环节出错。这一招可以从根源上区分“请求没找到 Servlet”和“Servlet 执行出错”两类问题。6. 进阶经验热部署、中文乱码和项目扩展方向6.1 热部署与重加载的使用心得IDEA 和 Tomcat 集成后默认情况下修改 Java 代码重新编译IDEA 会尝试热更新上下文但 Servlet 类这种容器级对象的更新经常不生效。实测最稳的方式是修改代码后如果只是改了 JSP、HTML、CSS 等静态资源点浏览器刷新就行如果改了 Java 类点运行配置里的 update 按钮或者干脆重启 Tomcat。不要过分依赖热部署尤其在加了 Servlet 注册新增WebServlet类后不重启大概率不生效。把热部署当辅助工具而不是全部学基础阶段宁可多按几次重启也别浪费时间等“不知道有没有生效”的状态。6.2 中文乱码的完整解法中文乱码在 Servlet 学习阶段几乎每个项目都会遇到。页面请求的乱码分三处浏览器发过来的中文参数、服务器返回的中文响应、以及日志控制台的中文输出。前两者最常用方案是// 设置请求编码处理 POST 请求体中的中文参数 req.setCharacterEncoding(UTF-8); // 设置响应编码和内容类型放最前面 resp.setContentType(text/html;charsetUTF-8);但要注意req.setCharacterEncoding对 GET 请求的 Query String 不一定生效因为 Query String 的编码取决于服务器 URIEncoding。保守的通用做法是在server.xml的 Connector 上增加URIEncodingUTF-8。虽然新版 Tomcat 默认已改为 UTF-8但了解这个原理能帮你排查到很多历史项目的难题。6.3 从 Servlet 到实际项目的扩展思考很多人学会 Servlet 后下一步就去撸 MVC 框架反而把这块基础丢了。其实 Servlet 是 Spring MVC 的基石DispatcherServlet 本身就是一个 Servlet。建议你在这个小项目的基础上自己扩展几个方向写一个登录功能用HttpSession保存用户状态体验会话管理。写一个请求转发和重定向的小例子搞懂forward和redirect的区别。写一个 Filter把请求日志统一打印出来理解过滤器链的执行顺序。后面这几个方向都会回到 Servlet 的底层知识点上。这篇文章里我没有刻意展开 Servlet 的生命周期原理但实操中你只要关注一个问题就够init只执行一次service每次请求都会执行。这个模型能解释很多开发中的诡异现象比如全局变量和实例变量的并发问题。最后分享一个小技巧每次修改server.xml或web.xml后一定记得看 logs 目录下的catalina.日期.log。Idea 控制台展示的日志做了截断处理很多隐藏的堆栈异常细节只有这个文件里才有完整记录。我排除了不少“灵异问题”最后都是在这个文件里找到了真正的 root cause。多花几十秒看日志比盲目重启十次管用得多。