小编典典

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

all

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

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

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


阅读 235

收藏
2022-02-25

共1个答案

小编典典

ServletContext

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

当 a 的Servleta<servlet><load-on- startup>@WebServlet(loadOnStartup)值大于0时,它的init()方法也会在启动期间使用一个新ServletConfig参数调用,该参数又包含所涉及的ServletContext。这些
servlet 以该值指定的相同顺序进行初始化(1第 1 次、2第 2 次等)。如果为多个 servlet 指定了相同的值,则这些 servlet
中的每一个都按照它们在 、 或 类加载中出现的相同web.xml顺序web-fragment.xml加载@WebServlet。如果“load-
on-startup”值不存在,那么只要HTTP
请求
第一次命中该
servlet,init()就会调用该方法。

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

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

HttpServletRequestHttpServletResponse

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

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

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

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

HttpSession

当客户端第一次访问 webapp 和/或第一次HttpSession通过记忆。servlet 容器还在HTTP
响应的标头中设置 a作为其名称,并将唯一会话 ID
作为其值。request.getSession()``HttpSession``session.getId()CookieSet-Cookie``JSESSIONID

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

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

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

简而言之

  • ServletContext只要网络应用程序存在,它就会存在。它在 所有 会话中的 所有 请求之间共享。 __
  • HttpSession只要客户端使用相同的浏览器实例与 Web 应用程序交互,并且会话在服务器端没有超时,这种情况就会一直存在。它在 同一 会话中的 所有 请求之间共享。 __
  • 并且从 servlet 接收HttpServletRequestHttpServletResponse来自客户端的 HTTP 请求开始,直到完整的响应(网页)到达。它 不在 其他地方共享。
  • 只要 Web 应用程序存在,所有Servlet,Filter和实例就会存在。Listener它们在 所有 会话中的 所有 请求之间共享。 __
  • 任何attribute在 中定义的ServletContextHttpServletRequest并且HttpSession只要有问题的对象存在,就会存在。对象本身代表 bean 管理框架(如 JSF、CDI、Spring 等)中的“范围”。这些框架将其作用域 bean 存储attribute为其最接近的匹配范围。

线程安全

也就是说,您主要关心的可能是 线程安全 。您现在应该知道
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.
    } 
}
2022-02-25