servlet 是如何工作的?实例化、会话、共享变量和多线程

假设,我有一个包含大量 servlet 的 Web 服务器。对于在这些 servlet 之间传递的信息,我正在设置会话和实例变量。

现在,如果 2 个或更多用户向此服务器发送请求,那么会话变量会发生什么情况?
它们对于所有用户都是通用的,还是对于每个用户都是不同的?
如果它们不同,那么服务器如何能够区分不同的用户?

还有一个类似的问题,如果有用户访问特定的 servlet,那么这个 servlet 只有在第一个用户第一次访问它时才会被实例化,还是会分别为所有用户实例化?
换句话说,实例变量会发生什么变化?n


答案 1

ServletContext

当 servlet 容器(如 Apache Tomcat)启动时,它将部署并加载其所有 Web 应用程序。加载 Web 应用程序时,Servlet 容器会创建一次 ServletContext,并将其保存在服务器的内存中。对 Web 应用程序和所有包含的文件进行解析,并且找到的每个 (或分别用 和 注释的每个类) 将被实例化一次,并保留在服务器的内存中,通过 .对于每个实例化筛选器,其方法都使用新的 FilterConfig 参数调用,该参数又包含所涉及的 。web.xmlweb-fragment.xml<servlet><filter><listener>@WebServlet@WebFilter@WebListenerServletContextinit()ServletContext

当 a 具有大于 或 的值时,则在启动期间也会使用新的 ServletConfig 参数调用其方法,该参数又包含所涉及的 。这些 servlet 按照该值指定的相同顺序进行初始化(即 1st、2nd 等)。如果为多个 servlet 指定了相同的值,那么每个 servlet 的装入顺序与它们在 、 或类装入中出现的顺序相同。如果“启动时加载”值不存在,则每当 HTTP 请求首次命中该 servlet 时,将调用该方法。Servlet<servlet><load-on-startup>@WebServlet(loadOnStartup)0init()ServletContext12web.xmlweb-fragment.xml@WebServletinit()

当 servlet 容器完成上述所有初始化步骤时,ServletContextListener#contextInitialized() 将使用一个参数调用,该参数又包含所涉及的 。这将允许开发人员有机会以编程方式注册另一个 或 。ServletContextEventServletContextServletFilterListener

当 servlet 容器关闭时,它会卸载所有 Web 应用程序,调用其所有初始化的 servlet 和过滤器的方法,并且所有 通过 注册的 实例都将被丢弃。最后,ServletContextListener#contextDestroyed() 将被调用,其本身将被丢弃。destroy()ServletFilterListenerServletContextServletContext

HttpServletRequestHttpServletResponse

Servlet 容器附加到一个 Web 服务器,该服务器侦听某个端口号上的 HTTP 请求(端口 8080 通常在开发期间使用,端口 80 在生产中使用)。当客户端(例如,使用Web浏览器的用户,或以编程方式使用URLConnection)发送HTTP请求时,Servlet容器会创建新的HttpServletRequestHttpServletResponse对象,并将它们传递到链中定义的任何对象,并最终传递实例。FilterServlet

对于筛选器,将调用该方法。当 servlet 容器的代码调用 时,请求和响应继续到下一个过滤器,或者如果没有剩余的过滤器,则命中 servlet。doFilter()chain.doFilter(request, response)

对于 servlet,将调用该方法。默认情况下,此方法根据 确定要调用的方法之一。如果 Servlet 中没有确定的方法,则在响应中返回 HTTP 405 错误。service()doXxx()request.getMethod()

请求对象提供对有关 HTTP 请求的所有信息的访问,例如其 URL标头查询字符串和正文。响应对象提供了以您想要的方式控制和发送HTTP响应的能力,例如,允许您设置标头和正文(通常使用从JSP文件生成的HTML内容)。提交并完成 HTTP 响应后,将回收请求和响应对象并使其可供重用。

HttpSession

当客户端第一次访问 webapp 和/或第一次通过 获得 HttpSession 时,servlet 容器会创建一个新对象,生成一个长而唯一的 ID(你可以通过它获得),并将其存储在服务器的内存中。Servlet 容器还在 HTTP 响应的标头中设置一个 Cookie,其名称为其名称,唯一会话 ID 作为其值。request.getSession()HttpSessionsession.getId()Set-CookieJSESSIONID

根据HTTP Cookie规范(任何体面的Web浏览器和Web服务器都必须遵守的合同),只要cookie有效,客户端(Web浏览器)就需要在标头中的后续请求中将此cookie发送回去(即唯一ID必须引用未过期的会话,并且域和路径是正确的)。使用浏览器的内置 HTTP 流量监视器,您可以验证 Cookie 是否有效(在 Chrome / Firefox 23+ / IE9+ 中按 F12,然后检查“网络/网络”选项卡)。Servlet 容器将检查每个传入 HTTP 请求的标头是否存在具有名称的 cookie,并使用其值(会话 ID)从服务器的内存中获取关联的 cookie。CookieCookieJSESSIONIDHttpSession

在空闲(即未在请求中使用)之前,将保持活动状态,其超时值超过 中指定的超时值。超时值默认为 30 分钟。因此,当客户端访问 Web 应用的时间不超过指定的时间时,servlet 容器将清除会话。每个后续请求,即使指定了cookie,也不再有权访问同一会话;servlet 容器将创建一个新会话。HttpSession<session-timeout>web.xml

在客户端,只要浏览器实例正在运行,会话 Cookie 就会保持活动状态。因此,如果客户端关闭浏览器实例(所有选项卡/窗口),则会话将在客户端被丢弃。在新的浏览器实例中,与会话关联的 Cookie 将不存在,因此将不再发送。这会导致创建一个全新的,使用一个全新的会话cookie。HttpSession

简而言之

  • 只要 Web 应用程序存在,它们就会存在。它在所有会话中的所有请求之间共享。ServletContext
  • 只要客户端与具有相同浏览器实例的 Web 应用交互,并且会话在服务器端未超时,该会话就会存在。它在同一会话中的所有请求之间共享。HttpSession
  • 从 servlet 从客户端收到 HTTP 请求到完整的响应(网页)到达为止。它不会在其他地方共享。HttpServletRequestHttpServletResponse
  • 所有 和 实例的生存时间与 Web 应用程序一样长。它们在所有会话中的所有请求之间共享。ServletFilterListener
  • 任何在 中定义的,并且只要所讨论的对象存在,它就会存在。对象本身表示Bean管理框架(如JSF,CDI,Spring等)中的“范围”。这些框架将其作用域的Bean存储为其最接近的匹配作用域。attributeServletContextHttpServletRequestHttpSessionattribute

螺纹安全

也就是说,您主要关心的可能是线程安全。您现在应该知道 servlet 和过滤器在所有请求之间共享。这就是Java的优点,它是多线程的,不同的线程(读取:HTTP请求)可以使用相同的实例。否则,重新创建它们的成本太高,并且它们对于每个请求都很重要。init()destroy()

您还应该意识到,永远不要将任何请求或会话范围的数据作为 servlet 或过滤器的实例变量分配。它将在其他会话中的所有其他请求之间共享。这不是线程安全的!下面的示例对此进行了说明:

public class ExampleServlet extends HttpServlet {

    private Object thisIsNOTThreadSafe;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;

        thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    } 
}

另请参阅:


答案 2

会话

enter image description hereenter image description here

简而言之:Web服务器在访问者首次访问时向其发出唯一标识符。访客必须带回该ID,以便下次识别他。此标识符还允许服务器将一个会话拥有的对象与另一个会话拥有的对象正确隔离。

Servlet 实例化

如果启动时加载为 false

enter image description hereenter image description here

如果启动时加载

enter image description hereenter image description here

一旦他进入服务模式和凹槽,相同的servlet将处理来自所有其他客户端的请求。

enter image description here

为什么每个客户端有一个实例不是一个好主意?想想看:你会为每笔订单雇佣一个披萨店吗?这样做,你很快就会破产。

不过,它带来了很小的风险。请记住:这个家伙把所有的订单信息都放在口袋里:所以如果你对servlet上的线程安全不谨慎,他最终可能会给某个客户下错订单。