Servlet 体系结构

发布于:2025-06-06 ⋅ 阅读:(20) ⋅ 点赞:(0)

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 专注业务。如此既减样板代码,又符合模板方法与责任分离的设计哲学

网站公告

今日签到

点亮在社区的每一天
去签到