我在带新人时经常看到这样的场景:照着网上零散的教程,用IDEA创建了一个Java项目,写了个Servlet类,点了Tomcat启动,看起来一切正常,结果浏览器一输地址就是404。更崩溃的是,有人做对了,有人做错了,两个人比较半天,发现IDEA版本、Tomcat版本、网上教程的版本全都不一样。这个教程就是想把这条线彻底理顺,用两种方法完整演示如何用IDEA创建一个Servlet程序,同时把背后的原理和坑一次性讲清楚。文章很长,但每一步都值得你亲手敲一遍。
适合谁看?刚学Java Web、对Servlet和Tomcat理解停留在"好像是这么回事"阶段的新手,以及会写Java但始终没搞明白Web项目是怎么被Tomcat跑起来的人。看完你不仅能成功创建Servlet项目,还能明白两种创建方式各自解决了什么问题。
1. 写Servlet之前,先把这三件事想明白:Servlet是什么、运行在哪儿、为什么有两种建法
很多新手一上来就急着点"New Project",这其实是最大的问题。Servlet不是普通Java类,它必须运行在Servlet容器里才有意义。在动手创建项目之前,我建议你先花十分钟把下面三件事搞明白。
1.1 Servlet的本质:它不是工具,是一个运行在Tomcat里的Java类
Servlet从定义上就是一个接口,你在IDEA里创建的Servlet程序,本质上就是写了一个类,实现了javax.servlet.Servlet接口,或者更常见的是继承HttpServlet这个适配类。它不像普通Java类那样拥有main方法,它的"入口"是容器回调的doGet、doPost这些方法。
打个比方:普通Java类像一个自己开店的老板,有main方法,自己决定干什么;Servlet则像一个入驻商场的店员,商场(Tomcat)负责开门、关门、接待顾客(HTTP请求),顾客来了,商场挑一个店员(Servlet实例)去处理。
这就是为什么你不能直接java HelloServlet来运行Servlet,必须把它打包成一个Web应用,放进Tomcat里,由Tomcat来加载和调用。
1.2 Tomcat、Servlet、Web应用三者的关系
这三个概念分清之后,后面部署就不会迷糊:
- Tomcat:一个Servlet容器,也是一个Web服务器。它负责监听端口、接收HTTP请求、找到对应Servlet、调用Servlet处理请求、把结果返回给浏览器。
- 你的Servlet程序:一个Java类,里面有处理业务逻辑的代码。它运行在Tomcat的JVM进程里,不是一个独立进程。
- Web应用:把Servlet类、HTML/JSP页面、配置文件、依赖jar包按照固定目录结构组装在一起的东西,通常以war包或解压后的目录形式存在。
Tomcat启动的时候,会扫描webapps目录下的Web应用,读取每个应用里的WEB-INF/web.xml,或者扫描Servlet类上的注解,建立起"URL路径 -> Servlet类"的映射关系表。只有这个表建好了,请求来了才知道该找谁。
1.3 为什么会有"两种方法":原生项目 vs Maven工程
标题里的"两种方法",指的是两种创建项目的路子,最终产物都是同一个Web应用,代码写起来也几乎一样,区别在工程组织方式:
| 对比项 | 方法一:原生项目 | 方法二:Maven项目 |
|---|---|---|
| 依赖管理 | 手动找jar包、手动复制 | pom.xml里写一行,自动下载 |
| 项目结构 | IDEA自己生成 | 按Maven标准目录生成 |
| 打包方式 | 靠IDEA的Artifact配置 | mvn package一键打war |
| 学习成本 | 低,适合理解原理 | 略高,但现代开发必学 |
| 实际工作使用 | 几乎不用 | 主流 |
方法一适合初学者理解Servlet运行机制,你能亲手摸到每一个文件,知道它们各有什么用;方法二适合直接进入现代开发节奏,毕竟现在工作里几乎没有不用Maven或Gradle的Java项目。我的建议是:先用方法一敲通一遍,再用方法二建一个同样的项目,两种方法都过一遍,你对Web项目程序的理解会比只学一种深得多。
1.4 环境准备:JDK、IDEA、Tomcat版本怎么配
新手经常在环境版本上栽跟头。这里直接给出我实测稳定的组合:
- JDK:8或11或17均可,建议直接用JDK 11或17,更接近现代项目。
- IDEA:Ultimate版或Community版都行。Servlet本身不需要IDEA的特殊功能,Community版完全能写。网上很多截图用的是Ultimate,界面略有不同,别慌。如果你只有Community版,刚好也能验证所有操作。
- Tomcat:建议用Tomcat 9.0.x。原因在后面讲Servlet依赖时细说——Tomcat 10开始包名从
javax变成了jakarta,新手极易踩坑。
IDEA从官网下载安装,一路默认即可,无需任何额外激活操作,社区版免费使用,Ultimate版有试用期。不要在网上找各种来路不明的包,安全第一。
Tomcat直接到官网下载zip包,解压后得到一个目录,就是后面要配置的Tomcat。注意解压路径最好别带空格和中文。
2. 方法一:不用Maven,纯手工搭建一个Servlet项目(最贴近底层原理)
这一节我们全程不借助Maven,用IDEA最基础的功能,把Servlet程序的每个零件手动拼起来。过程稍微繁琐,但拼完你会对整个Web应用结构有很直观的认识。
2.1 创建普通Java工程并给工程加上Web功能
第一步不是创建Web项目,而是创建一个普通Java项目,然后手动添加Web支持。这样做的目的是让你看清楚:一个Web项目,其实就是一个带有特殊目录结构和配置文件的Java项目。
在IDEA里操作:
- 点击
File -> New -> Project,左侧选Java,Project SDK选你装的JDK版本,下面不需要勾选任何模板,直接Next、Next,给项目起个名字,比如servlet-demo,完成。 - 项目创建好后,在项目名称上点击右键,选择
Add Framework Support...,在弹出的窗口里勾选Web Application。 - IDEA会自动在
src/main/下生成java目录(虽然是空的)、web目录,以及web/WEB-INF/web.xml文件。
这一步相当于在空壳项目里声明了"这是一个Web应用"。IDEA知道你后面要用这个目录结构来构建war包或者部署目录。
如果你用的是新版IDEA,右键菜单可能叫Add Framework Support或者需要在Project Structure里添加Web模块,本质上都一样。只要最后能看到web目录和web.xml就说明加成功了。
2.2 把servlet-api.jar加到工程里,这一步最容易被新手漏掉
你写Servlet类的时候需要用到javax.servlet下面的类,比如HttpServlet。这些类不在JDK里,它们来自Tomcat提供的一个jar包,叫servlet-api.jar。
不把这个jar包加进工程,你会看到上百个报错,所有继承了HttpServlet的类都找不到父类。新手在第一步就被劝退,多半就是卡在这里。
怎么办?两种方式,任选其一:
- 方式A:在Tomcat解压目录下的
lib文件夹里找到servlet-api.jar,复制到你的项目里,建议放到web/WEB-INF/lib目录下(这个目录专门放Web应用的依赖jar),然后在IDEA的Project Structure -> Libraries里把它添加进来。 - 方式B:在IDEA的
Project Structure -> Modules -> Dependencies里,点+号,选择Library,然后从Tomcat lib目录里选servlet-api.jar。这种方式不用复制文件,IDEA只是引用它。
这里有一个重要概念:编译和运行时的依赖差异。编译的时候,你的Servlet代码必须能看到HttpServlet这个类,所以需要servlet-api.jar。但真正运行时,HttpServlet这个类由Tomcat自己提供,Tomcat的lib目录里就有这个jar。所以这个jar在部署的时候不需要打进你的Web应用里,否则反而可能版本冲突。
提示:如果你把servlet-api.jar复制到了
web/WEB-INF/lib里,它运行时会和Tomcat自带的servlet-api冲突,可能抛出java.lang.NoSuchMethodError之类的异常。新手期最稳妥的做法是:编译期引用Tomcat的jar,不要复制进WEB-INF/lib。
2.3 写第一个Servlet类:继承HttpServlet并重写doGet/doPost
在src/main/java下新建一个包,比如com.example,然后在包里创建一个类HelloServlet。代码如下:
package com.example; 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; import java.io.PrintWriter; @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<h1>Hello Servlet!</h1>"); out.println("<p>这是你的第一个Servlet程序</p>"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }这段代码做了三件事:
- 通过
@WebServlet("/hello")注解,声明这个Servlet对应的访问路径是/hello。 - 重写
doGet方法,处理GET请求。response对象里装着要返回给浏览器的内容,getWriter()让你能像写字一样往响应里写HTML。 - 把
doPost直接转给doGet,这样POST请求也能被同样处理,这是新手友好的偷懒写法。
注意,这里我同时提到了注解和web.xml。实际上有了@WebServlet注解,下面的web.xml配置可以完全不写,两者选一个就行。但这一节我要把web.xml方式也讲清楚,因为这个文件是理解Web应用的关键。
2.4 告诉Tomcat哪些URL交给这个类处理:注解方式还是web.xml方式
访问地址和Servlet类之间的映射关系,历史上最早是写在一个叫web.xml的部署描述文件里的。Tomcat启动时会读这个文件。后来Servlet 3.0规范允许直接用注解,省掉XML配置。
如果你用注解,上面代码里的@WebServlet("/hello")就足够了。如果你想体验原始的配置方式,可以把注解注释掉,在web/WEB-INF/web.xml里加上:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>hello</servlet-name> <servlet-class>com.example.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>servlet标签先给类起个名字、注册到容器,servlet-mapping再把名字和URL路径关联起来。读完这段XML,你就知道Tomcat是怎么知道/hello要交给HelloServlet的了。
新手容易把这两个配置搞混或者重复配置。如果注解和web.xml都配了同一个路径,Tomcat启动时会报java.lang.IllegalArgumentException: Servlet mapping specifies an unknown servlet name或者重复映射相关的错误。记住一句话:注解和web.xml二选一,同时用时只能对应同一个逻辑位置,不要自己给自己挖坑。
我之前带过一个学员,两个都写了,但servlet-class写错成包名,结果Tomcat启动就报ClassNotFoundException,排查了很久才发现是包名拼错。新手排查这种问题的时候,先看控制台日志,不要瞎猜。
2.5 在IDEA里配置Tomcat并部署项目
代码写完了,接下来要把它跑起来。在IDEA里运行Tomcat,需要做两件事:把Tomcat关联到IDEA,把项目部署到Tomcat上。
- 打开
Run -> Edit Configurations,点左上角+号。 - 如果是新版IDEA,在列表里选择
Tomcat Server -> Local;如果列表里没有Tomcat选项,说明你的IDEA是社区版或者没有安装Tomcat插件,需要先生成Tomcat Server:点击+号,选择Tomcat Server,然后在Application server旁边点Configure...,选择Tomcat的解压目录。 - 配置好之后,切到
Deployment选项卡,点右上角+号,选择Artifact。 - 你会看到IDEA自动生成的artifact,名称形如
servlet-demo:war exploded。选中它,把它加进去。 - 确认
Application context里的路径,这个就是你访问应用的根路径。默认会填成/servlet-demo_war_exploded,太长了,新手经常在这里犯迷糊。建议改成/,这样直接访问根路径就能进应用。
配置完成后,点击绿色三角启动Tomcat。控制台刷出Server startup日志后,打开浏览器,输入:
http://localhost:8080/hello看到"Hello Servlet!",就说明方法一跑通了。
提示:如果用的是
/作为Application context,访问地址就是http://localhost:8080/hello。如果Application context是/servlet-demo,那访问地址就是http://localhost:8080/servlet-demo/hello。这个路径组合非常容易混,网上所有404问题里,一半以上是这个原因。建议把它们写在一个便签上,调试的时候对照着看。
3. 方法二:用Maven创建Servlet项目,工程化的玩法一次学会
方法一跑通后,Servlet对你来说已经不是神秘的黑盒了。但实际开发中,没人会手动复制jar包、手写Web应用的目录组装,Maven才是日常。这一节我们用Maven重新走一遍,你会看到同样的Servlet,在Maven工程里会简洁很多。
3.1 为什么要折腾Maven:依赖、打包、共享给别人
Maven的核心价值可以浓缩成三句话:
- 依赖自动管理:你在
pom.xml里写一行依赖坐标,Maven自动从中央仓库下载对应jar包,再也不用满世界找jar文件。 - 标准工程结构:源码、资源、测试目录都是约定好的,换一个人接手项目,不用适应他的目录习惯。
- 一键打包发布:
mvn package就能把项目打包成war文件,拿到任何一台装好Tomcat的机器上,丢进webapps目录就能跑。
方法一里你手动复制的servlet-api.jar,在Maven里只需要声明为依赖。而且Maven的依赖是写在配置文件里的,能跟着项目走,别人拿到你的项目,mvn一下,环境就齐了。
还有一点很重要:Maven项目的目录结构是约定大于配置,不再是IDEA随手生成的src/main/java加web,而是标准的:
src/ main/ java/ Java源码 resources/ 配置文件 webapp/ Web资源(等价于方法一里的web目录) pom.xml这个结构你在以后所有Maven项目里都会看到,提前适应没坏处。
3.2 用IDEA的Maven骨架创建web项目
IDEA创建Maven Web项目有两种姿势:
- 姿势A:通过Maven骨架创建。
File -> New -> Project -> Maven,勾选Create from archetype,选择maven-archetype-webapp。这种方式会生成一个标准的webapp骨架,自带src/main/webapp/index.jsp和pom.xml。 - 姿势B:先创建普通Maven项目,再手动补加
webapp目录。这种灵活但是要多配几步,新手不建议。
我建议用姿势A。注意,maven-archetype-webapp这个骨架生成的pom.xml里默认没有servlet依赖,它只有一个junit依赖,所以创建完项目后第一件事就是改pom.xml。
3.3 pom.xml里引入Servlet依赖,注意javax和jakarta的区别
这是整个方法二里最容易出错的地方。Servlet API的包名,在Tomcat 9及之前叫javax.servlet,从Tomcat 10开始改成了jakarta.servlet。如果你用Tomcat 9,却引入了Tomcat 10对应的jakarta.servlet-api,代码里import javax.servlet.http.HttpServlet就会全部标红。
Tomcat、Servlet版本和包名的对应关系如下:
| Tomcat版本 | Servlet规范版本 | 包名 | 依赖坐标示例 |
|---|---|---|---|
| Tomcat 8.5 | Servlet 3.1 | javax.servlet | javax.servlet:javax.servlet-api:3.1.0 |
| Tomcat 9.x | Servlet 4.0 | javax.servlet | javax.servlet:javax.servlet-api:4.0.1 |
| Tomcat 10.x | Servlet 5.0 | jakarta.servlet | jakarta.servlet:jakarta.servlet-api:5.0.0 |
| Tomcat 11.x | Servlet 6.0 | jakarta.servlet | jakarta.servlet:jakarta.servlet-api:6.0.0 |
所以,如果你按我前面的建议装了Tomcat 9,pom.xml里要加入的是:
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>这里scope设为provided,意思是:编译时我需要这个依赖,但运行时由Tomcat提供,不需要打包进war。这个和我在方法二里说的"servlet-api.jar不用打进Web应用"是一个道理。如果你忘了写provided,打包出来的war里就会带一份servlet-api,部署到Tomcat反而可能抛出ClassCastException或NoSuchMethodError。
3.4 写Servlet并部署运行,对比方法一的差异
在Maven项目的src/main/java下建包com.example,新建HelloServlet类。代码和方法一的完全一样,@WebServlet("/hello")加doGet重写。
不同之处在于:
- 不需要手动复制servlet-api.jar,Maven已经托管好了。
- 不需要手动配
web.xml(除非你向导里的web.xml是必需的,否则可以直接删掉,注解就够用)。 - 这是骨架项目自带一个
index.jsp,不用的话可以留着,也可以删掉,替换成你自己的页面。
运行前,同样需要配置Tomcat,步骤和方法一完全一样:Run -> Edit Configurations -> Tomcat Server -> Local,Deployment里添加Artifact。Maven工程的Artifact会自动生成,同样选war exploded即可。
启动后访问http://localhost:8080/hello,你会得到和方法一一模一样的页面结果。但注意观察IDEA的编译输出方式和项目结构,Maven工程的WEB-INF目录下已经自动帮你组织好了classes和lib,这就是Maven替你做的工作。如果你在Web应用的WEB-INF/lib里看到了servlet-api.jar,说明scope写错了,请回头改掉。
小技巧:第2步部署时,如果IDEA提示"没有Artifact",先检查Maven项目是否完成导入,在IDEA右侧Maven面板点刷新按钮,或者执行mvn package一次让target目录生成。这个方法对多数异常情况都有用,比瞎点运气好多了。
3.5 两种方式背后:同一个Web应用的本质
到这里,你可能会发现一个奇怪的现象:方法一和方法二操作流程高度同步,都是"创建项目 -> 写Servlet -> 配Tomcat -> 启动"。这根本不算巧合,它恰恰说明两种方法殊途同归——它们最终产出的都是同一个结构:WEB-INF/classes目录下的.class文件加WEB-INF/web.xml,扔到Tomcat里。
IDEA和Maven只是用不同手段帮你生成这个固定结构。你把方法一的web目录结构和方法二Maven的webapp目录结构并排对比一下,看到的是一模一样的骨架。理解了这一点,以后不管用什么工具,看到Web应用都不会再懵。
4. 两种方法的共同内核:一个Servlet请求到底是怎么跑起来的
很多教程讲完创建和运行就结束了,但我知道你心里还有疑惑:浏览器输入URL之后,后台到底发生了什么?为什么有时候控制台没报错,页面还是404?这一节把Servlet请求的完整链路讲清楚。
4.1 从浏览器输入URL到Servlet代码执行,中间发生了什么
假设你访问的是http://localhost:8080/hello,这里localhost:8080是Tomcat服务的地址和端口,/hello是请求路径。完整链路如下:
- Tomcat启动时扫描Web应用,根据
@WebServlet注解或web.xml里的配置,建立映射表:/hello -> com.example.HelloServlet。 - 浏览器发来请求,Tomcat的网络监听线程收到HTTP请求,解析方法、路径、请求头。
- Tomcat通过映射表找到对应的Servlet类。如果这个Servlet还没有实例,Tomcat会创建一个实例(默认单例,整个应用生命周期内只有一个对象实例,注意不是每次请求都new一个)。
- Tomcat创建两个关键对象:
HttpServletRequest(封装请求信息)和HttpServletResponse(封装响应信息)。 - Tomcat根据请求方法调用Servlet的
doGet或doPost。你写在方法里的代码开始执行。 - 你的代码通过
response.getWriter()往响应里写入HTML,Tomcat把这些内容包装成HTTP响应,通过Socket返回给浏览器。 - 浏览器拿到响应,解析HTML,渲染出页面。
4.2 Artifact和war exploded到底是个什么东西
你在IDEA里部署时看到的"war exploded"选项,对新手来说是个比较难以理解的名词。这里用大白话拆解一下:
- war包:一个压缩文件,把Web应用所有文件按标准结构压成一个包,后缀名是
.war。你可以把它理解成"方便运输的整箱货"。 - war exploded:war包的解压形态,即目录而非压缩文件。IDEA直接把编译后的
classes目录、webapp里的资源文件和WEB-INF目录组织成一个可运行的目录。这样做的好处是,改一行Java代码或页面,IDEA能快速增量更新,不用每次重新打包。工作里调试项目基本都用exploded模式。
你可以在IDEA的输出目录(默认在out/artifacts/)下面看到这个目录,亲眼看一眼它长什么样,对理解Web应用结构帮助极大:
servlet-demo_war_exploded/ META-INF/ MANIFEST.MF WEB-INF/ classes/ com/example/HelloServlet.class web.xml index.jsp这就是一个标准的Web应用。你把整个目录拷到一个Tomcat的webapps目录下,改名为servlet-demo,然后用bin目录下的startup.sh或startup.bat启动Tomcat,也能访问。IDEA那个绿色三角,本质上就是帮你把文件同步到Tomcat的部署位置,然后执行Tomcat启动命令,并没有额外魔法。
4.3 两种方法对比总结表
到这里,两种方法都用过一遍,我们把关键差异摆在一张表里,方便你以后查看:
| 对比项 | 方法一:原生项目 | 方法二:Maven项目 |
|---|---|---|
| 创建入口 | New Project -> Java | New Project -> Maven archetype |
| Web目录 | 手动Add Framework Support生成web目录 | 骨架自带webapp目录 |
| Servlet API来源 | 手动从Tomcat lib复制/引用 | pom.xml依赖 |
| web.xml | 手动维护 | 可留可删,注解优先 |
| 对新手友好度 | 偏底层,概念直观 | 偏工程,需理解Maven |
| 可维护性 | 差 | 好 |
5. 新手最容易踩的六个坑,附完整排查思路
下面这些坑,是我这些年看新人和自己调试时几乎绕不掉的。每个坑都用完整的排查链路来讲,而不是只给一句"你出错了"。
5.1 404:部署了却访问不到
现象:Tomcat正常启动,控制台没有任何报错,页面就是404。
按以下顺序排查:
- 检查访问的URL路径和Application context是否匹配。这是最频繁的原因。假设Application context是
/servlet-demo,那么完整路径是/servlet-demo/hello,不是/hello。 - 如果是某个具体Servlet路径404,而首页能打开,检查注解的
@WebServlet("/hello")路径是否以斜杠开头。缺少斜杠会映射异常。 - 看IDEA的Output窗口部署日志,确认Artifact是否真的部署成功。有时候点击Run后,你以为启动了,其实IDEA报了一个配置错误,但Tomcat旧进程还在跑。看日志里有没有
Deploying web application和Server startup两行,缺一不可。 - 检查浏览器访问的端口和Tomcat实际端口是否一致。IDEA里的Tomcat配置里
HTTP port默认8080,如果本地8080被占用,IDEA会换一个端口,比如8081,但浏览器还在访问8080,自然404。
5.2 ClassNotFoundException:明明写了Servlet类却说找不到
现象:启动Tomcat时报java.lang.ClassNotFoundException: com.example.HelloServlet,或者代码里所有HttpServlet都标红。
完整排查链路:
- 先确认
servlet-api.jar是否进入了编译路径。方法一里没有引入jar包,代码一定会标红,这是第一层。 - 如果代码不标红但运行时报ClassNotFoundException,检查你写的
<servlet-class>com.example.HelloServlet</servlet-class>里的包名和类名是否完全一致。刚开始学的时候,把com.example.HelloServlet写成com/example/HelloServlet,或者少了包名,都是我见过的真实错误。 - 确认你的类是否真的有
doGet或doPost且方法签名正确。如果方法签名不小心写成了public void doGet(...)(漏了protected或重写标识),Tomcat会认为这个类压根没处理GET请求,导致404而非异常。 - 方法二里,检查Maven依赖是否已经成功导入。IDEA的Maven面板里看得到依赖树,没有依赖的话刷新reimport。
- 最后才去排查Tomcat的classloader问题。新手遇到99%的情况是包名写错了和依赖没引入,不需要怀疑Tomcat本身。
5.3 8080端口被占用
现象:启动Tomcat时报Port 8080 was already in use。
原因很直白:你本机同时跑了另一个Tomcat、Nginx、或者别的东西占用了8080端口。
解决思路有两个:
- 把占用的进程杀掉(Windows下用
netstat -ano | findstr 8080找到PID,任务管理器结束进程;Mac/Linux下用lsof -i:8080)。 - 换一个端口。在IDEA的Tomcat配置里把
HTTP port改成8081或8090,访问URL跟着变即可。这是新手最省心的做法,适合不想跟系统进程较劲的时候。
5.4 重复映射导致启动失败
现象:Tomcat启动失败,catalina日志里出现类似Servlet [hello] and Servlet [hello] have the same mapping或java.lang.IllegalArgumentException。
原因是你同时用了@WebServlet("/hello")注解和web.xml里的<servlet-mapping>,并且两者都映射到了同一个路径。
这里要区分清楚:
- 同一个Servlet类,注解和web.xml重复配置,容器认为出现了两个名字不同但路径相同的Servlet,直接拒绝启动。
- 解决方式是二选一。新手阶段,建议直接全用注解,少维护一个配置文件。等你工作后如果项目用Spring Boot或Spring MVC,映射基本不在xml里写了。
5.5 IDEA版本不同界面不一样,怎么稳住
这是我在文章开头就提过的坑。IDEA从2020到2024,创建项目的入口、Tomcat配置界面、Artifact设置位置都改过多次。很多新人是按老截图操作的,找不到按钮就开始怀疑自己。
经验是:版本差异不改变核心步骤,只改变入口位置。你只需掌握每个步骤的目的,而不是死记按钮在哪个位置。举例:
- 目的是"声明这是一个Web应用",那无论入口叫
Add Framework Support还是Add Web,都不重要。 - 目的是"关联Tomcat",那你就去找
Edit Configurations里的+号,找不到Tomcat Server就去找插件市场装一个Tomcat插件,或者试试在项目里右键选择Run on Server,IDEA会自动弹出运行方式。
如果你实在卡住了,把IDEA左上角帮助菜单打开,搜索功能或快捷键Ctrl+Shift+A,直接搜"Tomcat"或"Artifact",不会有差。这个技巧对任何版本都通用。
5.6 修改Java代码后不生效
现象:你改了Servlet里的输出文字,重新点击Tomcat的运行按钮,浏览器刷新后还是旧内容。
原因通常是Tomcat是增量部署还是重启部署的问题,以及浏览器缓存。具体分析:
- 改了Java代码,会重新编译生成新的.class。但如果IDEA没有将新class同步到Tomcat的部署目录,你看到的自然是旧代码的结果。确认部署方式为
war exploded,这种模式改动Java类后重启Tomcat会同步新class,但如果是war模式,打包和部署步骤更多,容易不同步。 - 浏览器缓存也经常干扰。不改代码只刷新页面的时候,可以强制刷新或者开无痕窗口。
- 更简单粗暴的做法:第一次运行成功后,后续修改完代码,点击IDEA工具栏上的重启按钮(带旋转箭头的绿色按钮),而不是再次点击运行。重启会完整加载新代码,代价是启动多花几秒。
6. 我的最终建议:新手到底该用哪种方法入门
写到这里,我最后说一点个人建议。
如果你手头有充足的时间,请务必把方法一和方法二都完整敲一遍。方法一让Servlet变透明:你能亲手找到Tomcat的lib目录,亲手把servlet-api.jar加进编译路径,亲手看一遍war exploded目录结构。这些经历在方法二里会被Maven统统隐藏起来,表面上你省了事,但也省掉了理解底层的一次机会。方法二让你早一点适应现代Java开发的日常工作,认识Maven的构建逻辑、依赖机制和标准目录,这些能力会在你接触Spring Boot之后继续复用。
如果你时间紧张,只想快速跑通,就直接学方法二,但跑通之后一定回去看一遍方法一里web目录的结构说明。要明白,当你点击Tomcat启动的时候,IDE正在帮你把源码变成class、组装WEB-INF目录、拉起Tomcat进程,所有这些都不是魔法。
最后分享一个我平时带人必用的验证办法:等两种方法都成功后,从IDEA的项目输出目录里找到Web应用文件夹,手动复制一份到Tomcat的webapps目录,用Tomcat的bin目录脚本独立启动,不经过IDEA,看能不能访问成功。如果能,说明你对Web应用的部署逻辑已经真正过关了。这一步很多人会跳过,但只要做过一次,以后在Linux服务器上手动部署war包时,心里会很有底气。Servlet的入门说难不难,说简单也不简单,它确实是理解Java Web世界最值得花时间的一课。