1. 概述
本篇文章将带你从概念上理解什么是 Servlet 和 Servlet 容器,以及它们是如何协同工作的。
我们还会结合请求(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 被销毁,不再接收请求。这个从 init
到 service
再到 destroy
的过程,我们称之为 Servlet 的生命周期。
从容器的角度来看,比如 Apache Tomcat 或 Jetty,在启动时会创建一个 ServletContext
对象。
ServletContext
的作用是作为服务器的内存空间,用于记录与该 Web 应用相关的所有 Servlet、Filter 和 Listener,这些信息通常定义在 web.xml
或通过注解配置。
只要容器没有被关闭或重启,ServletContext
就会一直存在。
⚠️但这里有个关键点:Servlet 的 load-on-startup
参数。
- 如果该参数值大于 0,则容器在启动时就会初始化这个 Servlet。
- 如果没有设置这个参数,那么 Servlet 的
init()
方法会在第一次请求到达时才被调用。
3. 请求、响应与会话
在上一节中我们提到了请求与响应的概念,这是一切客户端-服务器通信的基础。下面我们来看看它们在 Servlet 中的表现形式。
在 Servlet 中:
- 请求对象是
HttpServletRequest
- 响应对象是
HttpServletResponse
每当客户端(比如浏览器或 curl)发起一个请求,容器就会创建一个新的 HttpServletRequest
和 HttpServletResponse
对象,并将它们传递给 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)的不同。
前面我们提到,不同对象的生命周期不同:
HttpServletRequest
和HttpServletResponse
:仅在一次请求中有效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