资讯详情

IDEA 2023下Servlet项目创建:环境搭配、Tomcat部署与底层原理

📅 2026/9/17 13:00:15 | 华诺云谱 👁 阅读
IDEA 2023下Servlet项目创建:环境搭配、Tomcat部署与底层原理
先回答一个很多人有过的疑问都 2023 年了IDEA 都能一键生成 Spring Boot 项目了为什么还要折腾 Servlet我在实际带项目、帮人排查问题的时候发现凡是 404 找不到类、过滤器不生效、拦截器失效、请求中文乱码这种问题最终都要回到 Servlet 这一层去看。Spring Boot 再方便它底层依然是 Servlet 容器那套请求-响应模型。框架把细节藏起来了但藏起来不等于不存在。用 IDEA 2023 创建 Servlet 项目不是为了复古而是为了亲手把这一层打通后面用框架时才不会心里发虚。这篇文章就围绕我自己的实操路径来写从环境搭配、完整创建流程、Tomcat 接入、Servlet 编写到常见坑和进阶玩法尽量把每一步背后的原因也讲清楚。适合两类人一是刚学 Java Web 想搞懂 Servlet 是什么的初学者二是用了一段时间框架但从来没看过底层、想回来补课的同学。1. 环境栈梳理JDK、IDEA 2023、Tomcat 与 Maven 的搭配逻辑创建 Servlet 项目之前最先要明确的是版本搭配。很多人项目建不出来或者建出来之后一运行就报错往往不是操作问题而是版本之间本来就互相不兼容。1.1 版本选型一定要先想清楚Servlet 项目其实不是一个独立运行的项目它必须跑在 Servlet 容器里。常见容器是 Tomcat也有 Jetty、Undertow。IDEA 2023 只是帮你写代码和部署的 IDE真正执行 Servlet 代码的是 Tomcat。这里最大的坑来自 Tomcat 版本变更。Tomcat 9 及之前的版本使用javax.servlet包名Tomcat 10 开始改为jakarta.servlet。这个变化是颠覆性的因为依赖的坐标、import 的包名、web.xml 的命名空间全部变了。我推荐的组合是这样的组件推荐版本说明JDK8 或 11 或 17Servlet 项目对 JDK 要求不高8 即可跑IDEA2023.1 及以上对 Maven、Tomcat 集成都稳定Tomcat9.0.x 或 10.1.x学习用推荐 9新项目可直接上 10.1Maven3.8.x 以上IDEA 自带 Maven 也可以建议用自己的本地包Servlet API与 Tomcat 匹配Tomcat 9 用 javax10 用 jakarta如果是学习 Servlet 基础和 Java Web 底层原理建议先选 Tomcat 9 javax.servlet。原因很简单网上的教程、老项目、绝大多数企业遗留代码都还是javax包名而且 Tomcat 9 的配置资料非常多。如果你从 Tomcat 10 开始会遇到大量旧教程import javax.servlet报红的尴尬情况。1.2 IDEA 2023 环境准备IDEA 2023 对 Servlet 项目没有特殊要求本地装了 JDK 和 Tomcat 就行。Tomcat 不需要在 IDEA 里安装IDEA 只是调用本机的 Tomcat。下载 Tomcat 时选zip版本解压到一个没有中文和空格的路径比如D:\apache-tomcat-9.0.85然后配置环境变量CATALINA_HOME指向这个目录。JDK 方面我建议直接用 JDK 8 起步。虽然 IDEA 2023 默认推荐 JDK 17 甚至更高但 Servlet 项目没有任何新语法需求JDK 8 足够而且兼容性上限最高。真要遇到老项目JDK 8 也更稳。Maven 可以先用 IDEA 自带的。打开 IDEA 2023在Settings - Build, Execution, Deployment - Build Tools - Maven里查看 Maven home path如果显示的还是 Bundled内置版本直接用即可日常开发多数场景足够。想用自己下载的 Maven 也简单把 Maven 解压后在 Maven home path 里选你的目录然后修改settings.xml的本地仓库路径避免默认仓库在 C 盘越堆越大。我第一次用的就是 Tomcat 10 旧教程的 javax 依赖结果 pom.xml 里加依赖没问题一写代码 import 全是红的。后来才明白这完全是两套命名空间。选版本之前先想清楚你参考的教程是基于哪个 Tomcat。2. 从零到能跑的完整创建流程环境准备好之后下面就是核心的创建过程。这里说的从零到能跑指的是在 IDEA 2023 里新建一个最简 Servlet 工程并且能部署到 Tomcat 看到Hello World。2.1 新建 Maven 项目而不是直接建 Java Project打开 IDEA 2023选择File - New - Project。左侧选择Maven右侧不要勾选 Create from archetype 里的任何模板先创建一个干净的 Maven 项目。为什么要这么做因为 IDEA 自带的 Maven 模板列表里虽然有maven-archetype-webapp但这个模板生成的 web.xml 版本非常老而且目录结构不完全符合 Servlet 规范后面还要手动调整反而不如从空项目开始自己把 Web 支持加上去。创建之后项目结构大概是这样的servlet-demo/ ├── pom.xml └── src/ ├── main/ │ └── java/ └── test/可以看到这里默认没有webapp目录。因为 IDEA 不知道你要做的是 Web 项目需要手动给它说明。2.2 添加 Web 支持并补全目录结构在项目名上右键选择Add Framework Support在弹出的窗口里勾选Web Application。IDEA 2023 里这个选项在Java Enterprise分组下面也可能在Web分组里具体取决于 IDEA 版本。勾选之后IDEA 会自动创建src/main/webapp目录并生成一个WEB-INF目录和web.xml文件。打开这个web.xml你会发现它默认的版本很高可能是 5.0 或者 6.0对应jakarta.servlet。如果你用的是 Tomcat 9需要把这个文件改成javax.servlet对应的版本格式。我以 Tomcat 9 Servlet 4.0 为例直接替换web.xml的内容?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 display-nameservlet-demo/display-name /web-app这一步很多人会漏掉。IDEA 默认生成的 web.xml 可能是 5.0 版本如果你把它直接扔到 Tomcat 9 上Tomcat 启动时会因为命名空间不匹配而报错。就算不报错它在解析注解时行为也可能偏掉。2.3 配置 pom.xml打包方式和 Servlet 依赖打开pom.xml首先要设置打包方式为war因为 Servlet 项目最终是以 war 包或者 exploded 目录形式部署到 Tomcat 的。然后加上javax.servlet-api依赖。project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdservlet-demo/artifactId version1.0-SNAPSHOT/version packagingwar/packaging properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency /dependencies build finalNameservlet-demo/finalName /build /project这里最关键的是scopeprovided/scope。因为 Tomcat 本身已经带了 Servlet API 的实现类如果你不设置 providedMaven 会把 Servlet API 的 jar 包也打进最终的 war 里导致 Tomcat 里出现两份相同类报ClassCastException或者各种诡异错误。2.4 在 src/main/java 下建包并写第一个 Servlet在src/main/java目录下新建一个包比如com.example.servlet然后写一个最基础的 Servlet 类package com.example.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.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/h1); } }注意这里的import javax.servlet前缀。如果你选择 Tomcat 10就必须改成jakarta.servlet。这一步也是新手最容易混淆的地方。我见过有人项目配置的是 Tomcat 10代码里却从网上抄了 javax 的写法结果一启动就是ClassNotFoundException。到这里一个最简项目已经成型了。但是光有项目还不够因为 IDEA 里项目要跑起来还差一个关键的部署配置。3. Tomcat 接入 IDEA 2023部署不是把 war 丢进去就完事把项目写好接下来要让它真正运行。这里告诉你 IDEA 2023 里完整的 Tomcat 接入流程以及每一步的配置动机。3.1 在 IDEA 里关联本地 Tomcat打开Run - Edit Configurations点击左上角加号找到Tomcat Server - Local。如果列表里没有 Tomcat Server可能是 IDEA 没有检测到 Application Servers需要先在Settings - Build, Execution, Deployment - Application Servers里点加号选 Tomcat Server然后指定 Tomcat 的安装目录。配置界面里比较重要的是这几个地方Application server选择你本地的 Tomcat 路径。Open browser默认会打开http://localhost:8080/你自己决定要不要自动打开。HTTP port默认 8080如果被占用可以改成 8081、9090 等。JMX port保持默认即可这是 IDEA 用来和 Tomcat 通信做热部署用的。配置完成后还没完这里只是告诉 IDEA Tomcat 在哪它还不知道你要把哪个项目部署进去。3.2 部署工件war 还是 war exploded在同一个配置界面的Deployment标签页点加号选择Artifact。你会看到两个选项servlet-demo:warservlet-demo:war explodedwar是把项目打包成压缩包再部署war exploded是直接把展开的目录部署过去。开发阶段一定要选war exploded因为这样 IDEA 才能做资源热更新。你改一个 JSP 页面点一下更新就能看到效果不需要反复打 war 包。如果选了 war每次改动都要重新打包开发效率低很多。选择war exploded之后下方会出现一个Application context配置默认是/servlet-demo_war_exploded太长我一般改成/servlet-demo。这个路径就是访问项目的根路径。也就是说访问HelloServlet的完整 URL 是http://localhost:8080/servlet-demo/hello其中/servlet-demo是应用上下文/hello是 Servlet 映射路径。两部分加在一起才是浏览器真正请求的地址。很多人部署后访问 404第一反应是 Servlet 映射写错了其实往往是 Application context 和映射路径叠出来跟预期不一致。3.3 热部署相关设置在同一个运行配置里还有Server标签页底部有个On frame deactivation下拉框。默认是Do nothing我建议在开发期改成Update classes and resources。这样当你切换到浏览器再切回 IDEA 时IDEA 会在后台把改动的 class 和资源文件同步到 Tomcat 的部署目录里省去手动点更新按钮的麻烦。但要注意热部署不是万能的。新增 Servlet 方法、修改了 web.xml、修改了依赖 jar这些操作通常需要重启 Tomcat 才能生效。热部署只对方法体内部的修改、JSP 页面修改、静态资源修改比较可靠。增量编译做不了的场景IDEA 会在日志里提示你重启或者部署失败所以也别把热部署当成免重启神器。4. 第一个 Servlet映射、生命周期与请求处理项目跑起来之后很多人的想法是能出 Hello 就行了但我建议还是把 Servlet 的几个核心知识点过一次因为后面排查全靠这里。4.1 Servlet 的三种映射方式Servlet 映射有三种常见方式注解、web.xml、动态注册。日常学习和开发中先用前两种就够。注解方式是 IDEA 2023 里最方便的直接在类上加WebServlet(/hello)容器启动时扫描到这个注解就会自动注册。这也是现在的主流用法。web.xml 配置方式适合老项目或需要集中管理映射的场景。如果你既写了注解又在 web.xml 里配置同一个路径启动时会报冲突。反过来如果web.xml根元素里设置了metadata-completetrueTomcat 会跳过注解扫描只有 web.xml 里的配置生效。这个问题我遇到过表现就是代码里明明写了WebServlet但访问一直是 404。如果你要走 web.xml 方式写法是这样的servlet servlet-nameHelloServlet/servlet-name servlet-classcom.example.servlet.HelloServlet/servlet-class /servlet servlet-mapping servlet-nameHelloServlet/servlet-name url-pattern/hello/url-pattern /servlet-mapping这里有个容易忽略的约束servlet-name必须一致路径要用/开头否则容器不认。4.2 Servlet 生命周期Servlet 的生命周期是容器管理的。流程很简单客户端请求到达如果 Servlet 还不存在Tomcat 会加载类、实例化、调用init()然后调用service()处理请求在容器关闭或者应用卸载时调用destroy()。init()默认是在第一次请求时才执行的但如果你希望 Tomcat 启动时就初始化可以在注解里加loadOnStartup 1或者在 web.xml 的servlet里加上load-on-startup1/load-on-startup。数值越小优先级越高。这个参数用在哪比如某个 Servlet 启动时需要加载缓存、初始化连接池这些耗时操作你不想等到第一个用户访问时才卡住。4.3 doGet、doPost 与中文字符编码处理常见的问题是我写了一个表单method 是 post但 Servlet 里只重写了doGet结果一提交就是 405 错误。service()方法会根据请求类型分发到对应的 doXxx 方法。没有重写对应方法时会调用父类 HttpServlet 的默认实现直接抛 405。更隐蔽的问题是中文乱码。Servlet 处理 POST 请求时如果参数里有中文接收端默认可能是 ISO-8859-1解码出来全是乱码。解决办法是在读取参数之前设置请求编码req.setCharacterEncoding(UTF-8);对于响应也要设置响应内容类型和编码resp.setContentType(text/html; charsetUTF-8);这两行代码位置很重要。setCharacterEncoding必须在第一次读取参数前调用否则不会生效setContentType最好在获取 Writer 之前调用。我还给过不少人一个建议如果项目里所有 Servlet 都需要设置编码不要每个类里重复复制粘贴用一个 Filter 统一处理。这其实也是 Spring 框架CharacterEncodingFilter的底层思路。4.4 转发与重定向的选择Servlet 处理完请求后经常需要跳页面。转发和重定向是两个概念区别不光在能不能看到 URL 变化更根本的是请求状态。转发是服务器内部跳转地址栏不变request 对象里的属性可以带到下一个 Servlet 或 JSP。用法是req.getRequestDispatcher(/success.jsp).forward(req, resp);重定向是让浏览器重新发起一次请求地址栏会变request 里的数据会丢。用法是resp.sendRedirect(req.getContextPath() /success.jsp);注意重定向的路径必须带上req.getContextPath()也就是项目的 Application context。如果不带部署到http://localhost:8080/servlet-demo下时重定向很可能跳到http://localhost:8080/success.jsp然后 404。这个细节在实际项目中非常常见。5. 我在 IDEA 2023 里踩过的坑每一条都真实这部分我打算用问题-现象-原因-解决的方式来写都是我在 IDEA 2023 环境下实际遇过的参考价值会比较高。5.1 启动后访问 404 的完整排查链路404 是 Servlet 入门路上最常遇到的问题。正常排查顺序是先确认 Tomcat 启动日志里有没有Deployment of web application archive或Deployment of web application directory这种成功语句确认项目部署成功。如果部署失败IDEA 的 Run 窗口会有异常堆栈。打开浏览器访问http://localhost:8080/servlet-demo/不带后面的路径看能否看到 Tomcat 默认页面。如果能说明 Tomcat 本身正常问题出在项目内部。再访问http://localhost:8080/servlet-demo/hello如果 404去检查 Servlet 类的WebServlet路径是不是/hello以及类是否在src/main/java目录下且被 Maven 编译。打开项目的target输出目录看classes里有没有对应的.class文件。没有就执行Build - Rebuild Project或者mvn clean compile。最后检查web.xml根元素的metadata-complete属性。如果为 true注解方式会被完全忽略。有一个现象特别坑IDEA 2023 中使用的部署工件如果名字不合理比如带_war_exploded后缀访问路径会变成/servlet-demo_war_exploded/hello容易让人误以为是 Servlet 路径写错。遇到 404先在 IDEA 的 Deployment 标签页看准 Application context 是什么再拼完整 URL。5.2 中文乱码一个请求里三种编码都要管Servlet 项目里的中文乱码可以出现在三个环节请求参数、响应输出、控制台日志。请求参数方面GET 请求的乱码取决于 Tomcat 的 URI 编码配置。Tomcat 8 之后默认 URI 编码是 UTF-8所以 GET 参数一般没问题。POST 请求则必须靠req.setCharacterEncoding(UTF-8)而且要在读取参数之前执行。响应输出方面除了resp.setContentType(text/html; charsetUTF-8)还要注意 IDEA 本身文件编码。在Settings - Editor - File Encodings里把项目编码、属性文件编码、默认编码都设为 UTF-8。否则就算代码里设置了响应编码源文件里的中文字符在被 javac 编译时就已经乱码了。控制台乱码是另一个常见问题。Tomcat 在 Windows 下启动时输出日志经常乱码这是因为 Tomcat 的日志输出编码默认是 UTF-8而 Windows 控制台默认是 GBK。解决办法是修改 Tomcat 安装目录下的conf/logging.properties文件把java.util.logging.ConsoleHandler.encoding从 UTF-8 改成 GBK或者直接在 IDEA 的 Help - Edit Custom VM Options 里加上-Dfile.encodingUTF-8。5.3 servlet-api 依赖 scope 没设 provided这个问题典型表现是本地开发一切正常IDEA 里也能跑但把 war 包放到独立的 Tomcat 上就出现类冲突、ClassNotFoundException或者IllegalAccessError。原因是 Maven 把 Servlet API 打进了 lib 目录。Tomcat 的 classloader 找到WEB-INF/lib下的一个 Servlet 实现类又在自己的 lib 目录里找到另一份两边的类不同强转时就会报错。解决办法就一条在 pom.xml 里把 Servlet API 的 scope 设为provided告诉 Maven 这个依赖我运行时不打包容器会提供。5.4 IDEA 2023 热部署不生效很多人改完代码切回浏览器刷新发现页面还是旧的。先别急着骂热部署按照顺序排查确认你部署的是war exploded。不是的话热部署没意义。Run配置里的On frame deactivation是否设置成了Update classes and resources。改动的内容是否涉及新增方法、新增类、修改 web.xml。这些改动需要重启。是否启用了 IDEA 的自动编译。到Settings - Build, Execution, Deployment - Compiler里把Build project automatically勾上否则 IDEA 不会自动生成新的 class。有一类特殊情况热部署之后服务一直报OutOfMemoryError: Metaspace。这是因为每次 reload 都会重新加载 class如果项目里用了反射、动态代理、自定义 ClassLoader类加载器无法完全释放内存就慢慢被占满。这个情况没有太好的根治方案最稳妥的还是定时重启。6. 进阶思路Servlet 怎么优雅调用大模型 API最后聊一个进阶话题。我知道不少人是被体验 Servlet 调用大模型 API 接口这个需求吸引来的。Servlet 虽然老但它作为后端 HTTP 入口的能力一点都不过时。特别是在不想引入完整 Spring 框架的场景里用 Servlet 去转发请求、聚合数据、做鉴权完全够用。6.1 为什么不在浏览器里直接调大模型 API现在很多大模型的 API 都有浏览器跨域限制即使支持跨域也会面临密钥暴露的风险。如果你在前端代码里直接写接口密钥那是灾难性的任何人都能从控制台里看到。Servlet 的价值就体现在这里浏览器只跟你的 Servlet 通信Servlet 在服务端持有密钥并请求大模型 API拿到结果后再返回给前端。这样就把密钥隔离在服务端了同时还能在 Servlet 层做一些额外逻辑比如记录请求日志、限制频率、统一异常处理。这其实是后端网关的一个极简雏形。6.2 同步调用与异步调用的选择最简单的方式是同步调用。Servlet 收到请求后用 Java 自带的java.net.http.HttpClient或者 Apache HttpClient 去请求大模型接口拿到结果后写回响应。但这里有个明显问题大模型接口的响应通常比较慢几秒到几十秒不等。Servlet 容器默认会分配一个线程来处理你的请求同步阻塞期间这个线程一直占着。如果你的项目并发量稍微高一点Tomcat 的线程池很快就会被占满后面的请求全部排队。更好的方式是利用 Servlet 3.1 的异步处理释放容器线程让容器尽快把这个请求挂起来等大模型结果准备好之后再恢复响应。示例结构大概是这样的WebServlet(urlPatterns /chat, asyncSupported true) public class ChatServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { AsyncContext asyncContext req.startAsync(); String prompt req.getParameter(prompt); // 用线程池处理耗时请求避免占用容器线程 Executor executor (Executor) req.getServletContext().getAttribute(executor); executor.execute(() - { try { String result callLLM(prompt); resp.setContentType(application/json; charsetUTF-8); resp.getWriter().write(result); } catch (Exception e) { resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } finally { asyncContext.complete(); } }); } }注意启动异步必须设置asyncSupported true同时要把 request 和 response 对象的使用全部放到异步线程里不能在主方法里关闭响应。6.3 流式返回从长轮询到 SSE大模型接口通常支持流式返回也就是一个字一个字往外吐。如果你用传统的同步响应方式就得等大模型全部输出完再返回用户等待时间很长体验很差。Servlet 3.1 也支持异步的 SSEServer-Sent Events方式可以把响应逐步写回浏览器。核心思路是最早用WebServlet加asyncSupported true拿到AsyncContext后在异步线程里循环读取大模型返回的流式数据每读一段就往response.getWriter()写一段并 flush。浏览器端用EventSource或者 fetch 的 ReadableStream 来接收。这个玩法写起来比想象中要复杂因为要处理连接断开、超时、异常恢复等问题。但这也正是学习 Servlet 异步机制最好的练手方向。等你能写通一个不需要任何框架的流式转发 Servlet 时再看 Spring WebFlux、响应式编程那些封装就会觉得底层的原理其实都是相通的。6.4 顺手封装一个简易的 HTTP 转发层如果你只是想在 Servlet 里做一个通用的大模型 API 网关可以按这个思路扩展建一个BaseApiServlet统一处理鉴权、日志、跨域响应头。子类实现doPost解析自己的入参格式。用 HttpClient 转发到真实的大模型 API并把响应流原样传回。这样一来后续接任何新的模型接口时你只需要新增一个 Servlet 子类控制入参出参的映射即可。你会发现 Servlet 这种一个类处理一类请求的模型配合注解组织起来其实很清晰。在这个扩展点上可以试很多有意思的功能比如把服务端的密钥管理起来给不同调用方分配不同的 API Key或者把请求日志写到本地文件排查线上问题甚至可以把大模型的响应做一层缓存相同问题短时间内直接返回缓存结果省掉 API 调用成本。我个人的经验是在 IDEA 2023 里创建 Servlet 项目这件事技术难度并不高真正决定你能不能顺利走下去的是版本搭配和对底层请求流转的理解。只要把 Tomcat 版本、Servlet API 包名、部署工件这三个关键点搞明白了后面写代码、加功能、排查问题都会顺畅很多。如果这篇文章里某个步骤你照着做还是跑不通优先检查 Tomcat 启动日志的报错那上面永远写着真正的原因。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。