假设,我有一个拥有大量 servlet 的 Web 服务器。对于在这些 servlet 之间传递的信息,我正在设置会话和实例变量。
现在,如果有 2 个或更多用户向此服务器发送请求,那么会话变量会发生什么?它们对所有用户都是通用的,或者对于每个用户而言都是不同的。如果它们不同,那么服务器如何区分不同的用户?
还有一个类似的问题,如果有n
用户访问特定的 servlet,那么这个 servlet 只在第一个用户第一次访问它时实例化,或者是否为所有用户分别实例化?换句话说,实例变量会发生什么?
当 servlet 容器(如Apache Tomcat )启动时,它将部署并加载其所有 Web 应用程序。加载 Web 应用程序时,servlet 容器会创建一次ServletContext
并将其保留在服务器的内存中。该 web 应用程序的web.xml
文件进行解析,并且每个<servlet>
<filter>
和<listener>
实测值(或带有加注解的每个类@WebServlet
, @WebFilter
和@WebListener
分别)被实例化一次并保持在服务器的存储器中作为好。对于每个实例化的过滤器,使用新的FilterConfig
调用其init()
方法。
当 servlet 容器关闭时,它会卸载所有 Web 应用程序,调用所有初始化的 servlet 和过滤器的destroy()
方法,并且所有ServletContext
, Servlet
, Filter
和Listener
实例都会被删除。
当Servlet
的<servlet><load-on-startup>
或@WebServlet(loadOnStartup)
值大于0
,其init()
方法也会在启动时使用新的ServletConfig
调用。这些小服务程序在由该值指定的相同顺序进行初始化( 1
是 1, 2
是第二,等等)。如果为多个 servlet 指定了相同的值,则每个 servlet 按照它们在web.xml
或@WebServlet
类加载中出现的顺序加载。如果没有 “load-on-startup” 值,只要 HTTP 请求第一次访问该 servlet,就会调用init()
方法。
servlet 容器连接到 Web 服务器,该服务器侦听特定端口号上的 HTTP 请求(端口 8080 通常在开发期间使用,端口 80 在生产中使用)。当客户端(例如,具有 Web 浏览器的用户或以编程方式使用URLConnection
)发送 HTTP 请求时,servlet 容器会创建新的HttpServletRequest
和HttpServletResponse
对象,并将它们传递给链中的任何已定义的Filter
,最终传递给Servlet
实例。
在filter的情况下,调用doFilter()
方法。当 servlet 容器的代码调用chain.doFilter(request, response)
,请求和响应将继续执行下一个过滤器,或者如果没有剩余过滤器则命中 servlet。
对于servlet ,调用service()
方法。默认情况下,此方法根据request.getMethod()
确定要调用哪个doXxx()
方法。如果 servlet 中没有确定的方法,则在响应中返回 HTTP 405 错误。
请求对象提供对 HTTP 请求的所有信息的访问,例如 URL,标题,查询字符串和正文。响应对象提供了以您希望的方式控制和发送 HTTP 响应的功能,例如,允许您设置标头和正文(通常使用 JSP 文件中生成的 HTML 内容)。提交并完成 HTTP 响应后,请求和响应对象都将被回收并可供重用。
当客户端第一次访问 webapp 和 / 或第一次通过request.getSession()
获取HttpSession
时,servlet 容器会创建一个新的HttpSession
对象,生成一个长且唯一的 ID(您可以通过session.getId()
获取) session.getId()
),并将其存储在服务器的内存中。 servlet 容器还设置Cookie
中Set-Cookie
与 HTTP 响应的报头JSESSIONID
正如它的名字和作为其值的唯一会话 ID。
根据HTTP cookie 规范 (任何体面的 Web 浏览器和 Web 服务器必须遵守的合同),只要 cookie 有效,客户端(Web 浏览器)就需要在Cookie
标头中的后续请求中发回此 cookie。 (即唯一 ID 必须指未到期的会话,域和路径是正确的)。使用浏览器的内置 HTTP 流量监视器,您可以验证 cookie 是否有效(在 Chrome / Firefox 23+ / IE9 + 中按 F12,然后选中Net / Network选项卡)。 servlet 容器将检查每个传入 HTTP 请求的Cookie
头是否存在名为JSESSIONID
的 cookie,并使用其值(会话 ID)从服务器的内存中获取关联的HttpSession
。
HttpSession
保持活动状态,直到它空闲(即未在请求中使用)超过<session-timeout>
( web.xml
的设置)中指定的超时值。超时值默认为 30 分钟。因此,当客户端访问 Web 应用程序的时间超过指定的时间时,servlet 容器会破坏会话。即使指定了 cookie,每个后续请求也将无法再访问同一个会话; servlet 容器将创建一个新会话。
在客户端,只要浏览器实例正在运行,会话 cookie 就会保持活动状态。因此,如果客户端关闭浏览器实例(所有选项卡 / 窗口),则会话在客户端被删除。在新的浏览器实例中,与会话关联的 cookie 将不存在,因此将不再发送。这会导致创建一个全新的HttpSession
,并使用一个全新的会话 cookie。
ServletContext
存在。它在所有会话中的所有请求之间共享。 HttpSession
存在,并且会话在服务器端没有超时。它在同一会话中的所有请求之间共享。 HttpServletRequest
和HttpServletResponse
从 servlet 接收来自客户端的 HTTP 请求开始,直到完整响应(网页)到达为止。它不在别处分享。 Servlet
, Filter
和Listener
实例都会存在。它们在所有会话中的所有请求之间共享。 ServletContext
, HttpServletRequest
和HttpSession
定义的任何attribute
都将存在。对象本身表示 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.
}
}
简而言之:Web 服务器在首次访问时向每个访问者发出唯一标识符。访问者必须带回该 ID,以便下次被识别。此标识符还允许服务器正确地将一个会话拥有的对象与另一个会话拥有的对象隔离。
如果load-on-startup为false :
如果load-on-startup为true :
一旦他处于服务模式和沟槽上, 同一个 servlet 将处理来自所有其他客户端的请求。
为什么每个客户端有一个实例不是一个好主意?想一想:你会为每一个订单雇用一个披萨店吗?这样做,你很快就会破产。
但它带来的风险很小。记住:这个单独的人把所有的订单信息放在口袋里:所以如果你对 servlet 的线程安全不小心,他最终可能会给某个客户提供错误的订单。
Java servlet 中的会话与其他语言(如 PHP)中的会话相同。它对用户来说是独一无二的。服务器可以用不同的方式跟踪它,例如 cookie,url 重写等。这篇Java doc文章在 Java servlet 的上下文中解释它,并指出会话的确切维护是服务器设计者留下的实现细节。规范仅规定必须通过与服务器的多个连接将其维护为对用户唯一。有关这两个问题的详细信息,请查看Oracle 的这篇文章 。
编辑这里有一个很好的教程,介绍如何在 servlet 中使用 session。而这里是来自 Sun 关于 Java Servlet 的,它们是什么,以及如何使用它们的一章。在这两篇文章之间,您应该能够回答所有问题。