Servlet 类图
---
title: Servlet 类图
---
classDiagram
direction LR
class Servlet {
<<interface>>
+init(conf)
+service(req,res)
+destroy()
}
class GenericServlet {
+getServletConfig()
+getServletContext()
}
Servlet <|-- GenericServlet
class HttpServlet {
+doGet()
+doPost()
+doPut()
+doDelete()
+doHead()
+doOptions()
}
GenericServlet <|-- HttpServlet
class LegacyServlet {
+init()
+doGet()
+destroy()
}
HttpServlet <|-- LegacyServlet
- Servlet:Servlet 体系根接口
- GenericServlet:Servlet 抽象实现类
- HttpServlet:对 HTTP 协议封装的 Servlet 实现类
SpringBoot 测试案例
创建 HttpServlet 测试类
@WebServlet("/demo")
public class ServletDemoSecond extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get...");
resp.setContentType("text/plain");
resp.getWriter().write("GET SUCCESS");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post...");
}
}
Servlet 规范不允许同一容器内多个 Servlet 映射到完全相同的 URL 路径,这会导致容器启动时抛出
IllegalArgumentException
。因为之前就是将 HttpServlet 测试类和 Servlet 测试类都制定同一个 URL,且 Servlet 测试类还设置了
loadOnStartup
导致了怎么都测试不了 HttpServlet 测试类
可以启动 SpringBoot 项目调用 /demo
接口进行测试,也可以通过 SpringBootTest 进行测试
需要以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.4.0</version>
</dependency>
创建测试类进行测试
@SpringBootTest(classes = DemoApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ServletDemoTest {
@LocalServerPort
private int port;
@Test
public void testDoGet() {
RestAssured.given().port(port).when().get("/demo").then().statusCode(HttpStatus.OK.value());
}
}
启动服务输出结果
get...
2025-06-04T21:42:40.711+08:00 INFO 7686 --- [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
2025-06-04T21:42:42.717+08:00 INFO 7686 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
Servlet 销毁
测试类输出结果
get...
HttpServlet 原理
伪代码理解原理
通过伪代码了解 Servlet 和 HttpServlet 的关系,通过创建 MyHttpServlet
类来理解
public class MyHttpServlet implements Servlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
// 根据请求方式不同,调用不同的方法
HttpServletRequest req = (HttpServletRequest) servletRequest;
// 获取请求方式
String method = req.getMethod();
// 判断请求方式
if ("GET".equals(method)) {
// GET 请求方式的处理逻辑
doGet(servletRequest, servletResponse);
} else if ("POST".equals(method)) {
// POST 请求方式的处理逻辑
doPost(servletRequest, servletResponse);
}
}
protected void doPost(ServletRequest servletRequest, ServletResponse servletResponse) {
System.out.println("调用 MyHttpServlet 的 doPost 方法");
}
protected void doGet(ServletRequest servletRequest, ServletResponse servletResponse) {
System.out.println("调用 MyHttpServlet 的 doGet 方法");
}
}
将之前的测试案例改成继承 MyHttpServlet
,即 public class ServletDemoSecond extends MyHttpServlet {}
@WebServlet("/demo")
public class ServletDemoSecond extends MyHttpServlet {
@Override
protected void doPost(ServletRequest servletRequest, ServletResponse servletResponse) {
super.doPost(servletRequest, servletResponse);
System.out.println("post...");
}
@Override
protected void doGet(ServletRequest servletRequest, ServletResponse servletResponse) {
super.doGet(servletRequest, servletResponse);
System.out.println("get...");
}
}
启动 SpringBoot 服务进行测试,控制台输出结果
调用 MyHttpServlet 的 doGet 方法
get...
理解差异
层级 | 是否协议相关 | service() 谁来实现 | 小结 |
---|---|---|---|
Servlet 接口 | 无 | 由 实现类 决定 | 只定义生命周期方法 init / service / destroy |
GenericServlet 抽象类 | 协议无关 | 仍然抽象→ 子类必须实现 | 把大量通用功能(getServletConfig()、getServletContext()、log() …)封装好 |
HttpServlet 抽象类 | 与 HTTP 绑定 | 自己实现,再分发到 doGet / doPost 等 | 你只需覆写需要的 “动词方法” |
GenericServlet
设计之初就强调 “Generic”——它既可能处理 FTP,也可能处理自定义二进制协议。没有任何通用逻辑可以写进 service()
,所以只能交给子类。
模版方法模式 Template Method:抽象类负责实现可复用的公共骨架(初始化,配置,日志),把变化点留给子类,实现开闭原则
为什么 HttpServlet
实现 service()
- 根据
HttpServlet
是针对于 HTTP 协议的 Servlet,HttpServlet.service()
可以统一读取request.getMethod()
,再路由到对应的doXXX()
方法 - 降低重复代码,如果
service()
还是抽象,所有业务 Servlet 都得自己写上述 switch 逻辑 → 代码膨胀且易错 - 因为已锁定 HTTP 协议,可以把“按方法分派”这一层通用逻辑封装好,再让你通过 doGet、doPost 专注业务。如此既减样板代码,又符合模板方法与责任分离的设计哲学