1. 概述

本篇文章将带你从概念上理解什么是 ServletServlet 容器,以及它们是如何协同工作的。

我们还会结合请求(Request)、响应(Response)、会话(Session)对象、共享变量以及多线程等上下文来深入探讨。

2. 什么是 Servlet 及其容器

Servlet 是 Java EE(JEE)框架中用于 Web 开发的一个组件。本质上来说,它是一个运行在容器中的 Java 程序。它的主要职责是:✅接收请求、✅处理请求、✅返回响应。

在使用之前,Servlet 需要先进行注册,这样无论是基于 JEE 还是 Spring 的容器,才能在启动时识别并加载它们。

容器在启动时会调用 Servlet 的 init() 方法完成初始化。初始化完成后,Servlet 就可以接收请求了。

每当有请求进来,容器会将请求交给 Servlet 的 service() 方法处理。然后根据 HTTP 请求类型(GET、POST 等),进一步调用对应的 doGet()doPost() 方法。

当容器调用 destroy() 方法时,Servlet 被销毁,不再接收请求。这个从 initservice 再到 destroy 的过程,我们称之为 Servlet 的生命周期

从容器的角度来看,比如 Apache TomcatJetty,在启动时会创建一个 ServletContext 对象。

ServletContext 的作用是作为服务器的内存空间,用于记录与该 Web 应用相关的所有 Servlet、Filter 和 Listener,这些信息通常定义在 web.xml 或通过注解配置。

只要容器没有被关闭或重启,ServletContext 就会一直存在。

⚠️但这里有个关键点:Servlet 的 load-on-startup 参数。

  • 如果该参数值大于 0,则容器在启动时就会初始化这个 Servlet。
  • 如果没有设置这个参数,那么 Servlet 的 init() 方法会在第一次请求到达时才被调用。

3. 请求、响应与会话

在上一节中我们提到了请求与响应的概念,这是一切客户端-服务器通信的基础。下面我们来看看它们在 Servlet 中的表现形式。

在 Servlet 中:

每当客户端(比如浏览器或 curl)发起一个请求,容器就会创建一个新的 HttpServletRequestHttpServletResponse 对象,并将它们传递给 Servlet 的 service() 方法。

根据请求对象中的方法类型(GET、POST 等),service() 方法会选择调用相应的 doGet()doPost() 方法。

除了方法类型,请求对象还包含了请求头、参数和请求体等信息。响应对象也类似,我们可以在 doXXX 方法中设置响应头、状态码和响应体。

⚠️这两个对象都是短生命周期的。一旦客户端收到响应,服务器就会将它们标记为可回收对象。

那如果我们要在多个请求之间维持状态怎么办?答案是:使用 HttpSession

Session 通过绑定对象到用户会话中,实现跨请求的数据持久化。通常是通过 Cookie 实现,其中 JSESSIONID 作为会话的唯一标识符。

我们可以在 web.xml 中设置会话超时时间:

<session-config>
    <session-timeout>10</session-timeout>
</session-config>

这意味着如果一个会话在 10 分钟内没有任何活动,服务器就会将其销毁。之后的请求会创建一个新的会话。

4. Servlet 之间如何共享数据

Servlet 之间共享数据的方式取决于作用域(scope)的不同。

前面我们提到,不同对象的生命周期不同:

  • HttpServletRequestHttpServletResponse:仅在一次请求中有效
  • HttpSession:在会话有效期内有效
  • ServletContext:与 Web 应用同生命周期 ✅最长

所以:

  • 如果需要在整个应用中共享数据(如统计网站访问人数),应该使用 ServletContext
  • 如果需要在用户会话中共享数据(如用户名),应该使用 HttpSession
  • 如果只是单次请求中传递数据(如表单参数),使用 request.setAttribute()

5. 多线程处理

Servlet 是多线程共享的。多个请求可以同时访问同一个 Servlet 实例,但每个请求运行在自己的线程中。

⚠️这就带来了一个线程安全问题:不要把请求或会话级别的数据作为 Servlet 的实例变量

举个 ❌错误示例:

public class ExampleThree extends HttpServlet {
    
    private String instanceMessage;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
      throws ServletException, IOException {
        String message = request.getParameter("message");
        instanceMessage = request.getParameter("message"); // ❌线程不安全
        request.setAttribute("text", message); // ✅安全
        request.setAttribute("unsafeText", instanceMessage); // ❌不安全
        request.getRequestDispatcher("/jsp/ExampleThree.jsp").forward(request, response);
    }
}

在这个例子中:

  • message 是局部变量,线程安全 ✅
  • instanceMessage 是实例变量,多个请求共享,会导致数据混乱 ❌

6. 总结

在这篇文章中,我们介绍了 Servlet 和 Servlet 容器的基本概念,以及围绕它们的几个核心对象(请求、响应、会话等)。还讲解了 Servlet 之间的数据共享方式和多线程下的注意事项。

源码可从 GitHub 获取:https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-mvc-xml


原始标题:Introduction to Servlets and Servlet Containers