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

新手上路,请多包涵

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

现在,如果 2 个或更多用户向该服务器发送请求,那么会话变量会发生什么变化?

它们对所有用户都是通用的还是对每个用户都不同?

如果它们不同,那么服务器如何区分不同的用户?

还有一个类似的问题,如果有 n 用户访问特定的 servlet,那么这个 servlet 只会在第一个用户第一次访问它时被实例化,还是会分别为所有用户实例化?

换句话说,实例变量会发生什么?

原文由 Ku Jon 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 346
2 个回答

ServletContext

当 servlet 容器(如 Apache Tomcat )启动时,它将部署并加载其所有 Web 应用程序。加载 Web 应用程序时,servlet 容器会创建一次 ServletContext 并将其保存在服务器的内存中。 The web app’s web.xml and all of included web-fragment.xml files is parsed, and each <servlet> , <filter> and <listener> 找到(或分别用 @WebServlet , @WebFilter@WebListener 注释的每个类)将被实例化一次并通过 ServletContext 内存保存在服务器中 --- 。对于每个实例化的过滤器,它的 init() 方法使用新的 FilterConfig 参数调用,该参数又包含相关的 ServletContext

When a Servlet has a <servlet><load-on-startup> or @WebServlet(loadOnStartup) value greater than 0 , then its init() method is also invoked在使用新的 ServletConfig 参数启动期间,该参数又包含所涉及的 ServletContext 。这些 servlet 以该值指定的相同顺序初始化( 1 是第一个, 2 是第二个,等等)。如果为多个 servlet 指定了相同的值,那么每个 servlet 的加载顺序与它们在 web.xmlweb-fragment.xml@WebServlet 类加载。如果“启动时加载”值不存在,每当 HTTP 请求 第一次访问该 servlet 时,将调用 init() 方法。

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

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

HttpServletRequestHttpServletResponse

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

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

servlet 的情况下,将调用 service() 方法。默认情况下,此方法根据 --- 确定调用 --- request.getMethod() doXxx() 方法中的哪一个。如果 servlet 中不存在确定的方法,则在响应中返回 HTTP 405 错误。

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

HttpSession

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

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

HttpSession 一直保持活动状态,直到它空闲(即未在请求中使用)超过 <session-timeout> 中指定的超时值,该设置在 web.xml 中指定。超时值默认为 30 分钟。因此,当客户端在超过指定时间的情况下未访问 Web 应用程序时,servlet 容器会丢弃 会话。每个后续请求,即使指定了 cookie,也将无法再访问同一会话; servlet 容器将创建一个新会话。

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

简而言之

  • ServletContext 与网络应用程序一样存在。它在 所有 会话中的 所有 请求之间共享。
  • HttpSession 只要客户端使用相同的浏览器实例与 Web 应用程序交互,并且会话在服务器端没有超时,就会一直存在。它在 同一 会话中的 所有 请求之间共享。
  • HttpServletRequestHttpServletResponse 从 servlet 收到来自客户端的 HTTP 请求开始,直到完整的响应(网页)到达。它 不会 在其他地方共享。
  • 所有 ServletFilterListener 实例与网络应用程序一样存在。它们在 所有 会话中的 所有 请求之间共享。
  • Any attribute that is defined in ServletContext , HttpServletRequest and HttpSession will live as long as the object in question lives.对象本身代表 Bean 管理框架(例如 JSF、CDI、Spring 等)中的“范围”。这些框架将其范围内的 bean 存储为其最接近的匹配范围的 attribute

线程安全

也就是说,您主要关心的可能是 thread safety 。您现在应该知道 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.
    }
}

也可以看看:

原文由 BalusC 发布,翻译遵循 CC BY-SA 4.0 许可协议

会话

在此处输入图像描述在此处输入图像描述

简而言之:网络服务器会在 每位访问者 首次 访问时为其分配一个唯一标识符。访客必须带回该 ID,以便下次被识别。该标识符还允许服务器正确地将一个会话拥有的对象与另一个会话拥有的对象分开。

Servlet实例化

如果 启动时加载false

在此处输入图像描述在此处输入图像描述

如果 启动时加载

在此处输入图像描述在此处输入图像描述

一旦他进入服务模式并处于最佳状态, 同一个 servlet 将处理来自所有其他客户端的请求。

在此处输入图像描述

为什么每个客户端一个实例不是一个好主意?想一想:您会为每一份订单雇用一个比萨店员吗?那样做,你很快就会倒闭。

虽然它有一个小风险。记住:这个人把所有的订单信息都放在他的口袋里:所以如果你 对 servlet 的线程安全不谨慎,他可能最终会向某个客户发出错误的订单。

原文由 Jops 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题