假设,我有一个包含大量 servlet 的网络服务器。对于在这些 servlet 之间传递的信息,我正在设置会话和实例变量。
现在,如果 2 个或更多用户向该服务器发送请求,那么会话变量会发生什么变化?
它们对所有用户都是通用的还是对每个用户都不同?
如果它们不同,那么服务器如何区分不同的用户?
还有一个类似的问题,如果有 n
用户访问特定的 servlet,那么这个 servlet 只会在第一个用户第一次访问它时被实例化,还是会分别为所有用户实例化?
换句话说,实例变量会发生什么?
原文由 Ku Jon 发布,翻译遵循 CC BY-SA 4.0 许可协议
ServletContext
当 servlet 容器(如 Apache Tomcat )启动时,它将部署并加载其所有 Web 应用程序。加载 Web 应用程序时,servlet 容器会创建一次
ServletContext
并将其保存在服务器的内存中。 The web app’sweb.xml
and all of includedweb-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 than0
, then itsinit()
method is also invoked在使用新的ServletConfig
参数启动期间,该参数又包含所涉及的ServletContext
。这些 servlet 以该值指定的相同顺序初始化(1
是第一个,2
是第二个,等等)。如果为多个 servlet 指定了相同的值,那么每个 servlet 的加载顺序与它们在web.xml
、web-fragment.xml
或@WebServlet
类加载。如果“启动时加载”值不存在,每当 HTTP 请求 第一次访问该 servlet 时,将调用init()
方法。当 servlet 容器完成上述所有初始化步骤后,将使用
ServletContextListener#contextInitialized()
参数调用ServletContextEvent
参数,该参数又包含涉及的ServletContext
.这将使开发人员有机会以编程方式注册另一个Servlet
、Filter
或Listener
。当 servlet 容器关闭时,它会卸载所有 Web 应用程序,调用
destroy()
所有已初始化的 servlet 和过滤器的方法,以及所有Servlet
,Filter
Listener
118d2fc5---
通过ServletContext
注册的实例被丢弃。最后ServletContextListener#contextDestroyed()
将被调用并且ServletContext
本身将被丢弃。HttpServletRequest
和HttpServletResponse
servlet 容器附加到 Web 服务器,该服务器在特定端口号(开发期间通常使用端口 8080,生产中通常使用端口 80)上侦听 HTTP 请求。当客户端(例如,使用 Web 浏览器的用户,或 以编程方式使用
URLConnection
)发送 HTTP 请求时,servlet 容器创建新的HttpServletRequest
和HttpServletResponse
对象它们通过链中任何定义的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 应用程序交互,并且会话在服务器端没有超时,就会一直存在。它在 同一 会话中的 所有 请求之间共享。HttpServletRequest
和HttpServletResponse
从 servlet 收到来自客户端的 HTTP 请求开始,直到完整的响应(网页)到达。它 不会 在其他地方共享。Servlet
,Filter
和Listener
实例与网络应用程序一样存在。它们在 所有 会话中的 所有 请求之间共享。attribute
that is defined inServletContext
,HttpServletRequest
andHttpSession
will live as long as the object in question lives.对象本身代表 Bean 管理框架(例如 JSF、CDI、Spring 等)中的“范围”。这些框架将其范围内的 bean 存储为其最接近的匹配范围的attribute
。线程安全
也就是说,您主要关心的可能是 thread safety 。您现在应该知道 servlet 和过滤器在所有请求之间共享。这是 Java 的优点,它是多线程的,不同的线程(阅读:HTTP 请求)可以使用同一个实例。否则重新创建
init()
和destroy()
它们对于每个请求来说都太昂贵了。您还应该意识到,您 永远 不应将任何请求或会话范围内的数据分配为 servlet 或过滤器的 实例 变量。它将在其他会话中的所有其他请求之间共享。那 不是 线程安全的!下面的例子说明了这一点:
也可以看看: