我平时挺反感一上来就背定义,但 Servlet 这章不一样——只要你想做 Java Web 开发,无论以后用不用框架,它都绕不开。这篇文章不是抄文档,是从“我实际怎么把 Servlet 跑起来并理解它”的角度去写,重点放在 web.xml 方式编写 Servlet 的完整示例,适合正在学 Java Web、刚接触 Tomcat、以及看着 Spring Boot 源码觉得底子不够扎实的同学。代码、配置、排查思路我都会给全,建议你跟着建一个最朴素的项目,亲手动一遍。
1. 为什么现在还要翻开 Servlet 这一页
1.1 Servlet 与 Tomcat 的包含关系
你写一个能处理 HTTP 请求的 Java 程序,会遇到一个麻烦:HTTP 协议本身是文本格式的,你得自己监听端口、解析请求头、拼响应报文。这种重复劳动没有任何技术含量,却占大多数时间。
Tomcat 这类 Web 容器把脏活都干了。它监听 8080 端口,拿到浏览器发来的 HTTP 请求后,解析成 Java 对象,再调用你的业务代码;你只管往响应对象里写内容,剩下的由容器帮你打包成 HTTP 响应发回去。
Servlet 就是“你的业务代码”和“容器的调用规则”之间的约定。
换句话说,Tomcat 不认识你的自定义类,它只认实现了 javax.servlet.Servlet 接口(或继承自 HttpServlet)的类。你写一个类,按规范继承 HttpServlet,在 web.xml 里告诉容器“什么 URL 交给这个类处理”,容器一旦收到匹配的请求,就会自动帮你创建实例并调用对应方法。
这套模型的核心价值在于:你不需要关心网络层,只需要关心业务逻辑。听起来理所当然,但在 Servlet 出现之前,Java 想做 Web 开发需要用 CGI 之类的方案,每个请求起一个进程,性能和开发体验都很差。Servlet 通过“驻留内存 + 多线程处理”的方式,把 Web 开发拉回了普通 Java 编程的轨道。
1.2 框架只是 Servlet 的“壳”
很多人绕开 Servlet 直接学 Spring Boot,觉得“反正现在没人手写 Servlet 了”。这个想法短期能应付增删改查,但一遇到深层问题就露馅。
Spring MVC 的核心入口 DispatcherServlet,本质上就是一个 Servlet。它被定义在 Spring 的 jar 包里,配置文件里注册到 web.xml(或者通过自动配置注册),所有请求先到这个 Servlet,再由它分发到各个 @Controller 方法。
当你明白了这层关系,很多“神奇”的现象就有了解释:
- 为什么过滤器(Filter)会比
@Controller先执行?因为 Filter 是 Servlet 规范里的组件,它在请求到达 DispatcherServlet 之前就拦截了。 - 为什么静态资源要单独配置?因为 DispatcherServlet 默认映射
/,会把静态资源请求也拦截,需要你再配置一个默认 Servlet 放行。 - 为什么启动时要加载一些全局配置?这跟
ServletContextListener的触发时机有关。
我一直跟新人讲,学框架别只背注解,要能回答出“框架底层调用了哪些 Servlet 规范的 API”。把这一章吃透,后面看 Spring、Shiro、Shiro 的 Filter 机制,都会轻松很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:从一个手工 Web 项目开始,跑通第一个 HTTP 周期
2.1 开工前工具搭配与版本
先明确版本,这是很多人掉坑的第一步。
我用的是:
- JDK 8(Servlet 4.0 完全够用;如果你喜欢 JDK 11 或 17 也没问题,但要知道 Tomcat 版本要匹配)
- Tomcat 9.x
- IntelliJ IDEA(社区版和旗舰版都行)
- Maven 3.6+
为什么特别强调 Tomcat 9?因为从 Tomcat 10 开始,Servlet API 的包名从 javax.servlet 换成了 jakarta.servlet。网上大量旧教程、旧代码、旧依赖全是 javax 开头,你用 Tomcat 10 去跑旧代码,经常会遇到 ClassNotFoundException: javax.servlet.http.HttpServlet 这种错误。
初学阶段用 Tomcat 9 最省心,教程和依赖都全面。等完全理解 Servlet 规范之后,再切换到 Tomcat 10/11 了解 jakarta 命名空间,也不迟。
2.2 创建最小项目结构与依赖
我建议不要一开始就依赖 IDEA 的“Java Enterprise”模板,可以先建一个最朴素的 Maven 项目,手动把目录补成 Web 结构,这样每个文件在哪、为什么在那,你心里才有数。
项目最终会长这样:
code复制servlet-chapter
├── pom.xml
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── demo
│ │ └── web
│ │ └── HelloServlet.java
│ └── webapp
│ └── WEB-INF
│ └── web.xml
在 pom.xml 里引入 Servlet API 依赖:
xml复制<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
有一点需要说明:scope 设为 provided,意思是编译时需要,但打包进 war 时不要带进去。因为 Tomcat 自己已经有一份 Servlet API 实现,如果你把依赖打进去,反而可能与容器自带的类冲突。
如果你想把项目打包成 war 放到 Tomcat 的 webapps 目录下运行,记得加一个 war 插件配置:
xml复制<build>
<finalName>servlet-chapter</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
这里的 finalName 很重要,它决定了解压后的应用目录名,也就是 URL 里的 Context Path。如果打成 servlet-chapter.war,访问路径就是 http://localhost:8080/servlet-chapter/...。
2.3 拿到一份合理的 web.xml 骨架
在 src/main/webapp/WEB-INF/web.xml 创建一个空配置文件。Servlet 4.0 对应的 web.xml 头部是这样:
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">
<display-name>servlet-chapter</display-name>
</web-app>
这个骨架平时看起来不起眼,但里面能配置的东西非常多:Servlet 映射、Filter 顺序、监听器、欢迎页、错误页、会话超时时间、全局参数,全都要写在这里。
我遇到过不少同学,拿着一个没有 web.xml 的 Maven Web 项目就开始写注解版 Servlet,虽然能跑,但等到需要配置 Filter 顺序、配置错误页的时候,又不知道怎么兜底。老项目和新项目混杂,web.xml 的配置能力是必须掌握的。
3. 用 web.xml 方式完成示例:从 Servlet 类到浏览器上的一句话
3.1 最小可运行的 Servlet 类
这一步直接对应你在搜索引擎里看到的那个热词:使用 web.xml 方式编写 Servlet 的完整示例。我把代码写全,然后你照着敲一遍。
先在 com.demo.web 包下新建 HelloServlet.java:
java复制package com.demo.web;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class HelloServlet extends HttpServlet {
public HelloServlet() {
System.out.println("HelloServlet 构造方法执行");
}
@Override
public void init() throws ServletException {
System.out.println("HelloServlet init() 执行");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/
