假设,我有一个包含许多 servlet 的网络服务器。对于在这些 servlet 之间传递的信息,我正在设置会话和实例变量。
现在,如果 2 个或更多用户向该服务器发送请求,那么会话变量会发生什么情况? 它们对所有用户都是通用的,还是对每个用户都不同? 如果它们不同,那么服务器如何区分不同的用户?
还有一个类似的问题,如果有n用户访问一个特定的 servlet,那么这个 servlet 只会在第一个用户第一次访问它时被实例化,还是单独为所有用户实例化? 换句话说,实例变量会发生什么?
n
ServletContext
当 servlet 容器(如Apache Tomcat)启动时,它将部署并加载其所有 Web 应用程序。加载 Web 应用程序时,servlet 容器会创建ServletContext一次并将其保存在服务器的内存中。Web 应用程序web.xml和所有包含的web- fragment.xml文件都被解析,每个<servlet>,<filter>和找到的(或每个分别用,和<listener>注释的类)将被实例化一次并保存在服务器的内存中,通过. 对于每个实例化的过滤器,它的方法被一个新参数调用,该参数又包含所涉及的.@WebServlet``@WebFilter``@WebListener``ServletContext``init()FilterConfigServletContext
web.xml
web- fragment.xml
<servlet>
<filter>
<listener>
@WebServlet``@WebFilter``@WebListener``ServletContext``init()
FilterConfig
当 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
<servlet><load-on- startup>
@WebServlet(loadOnStartup)
0
init()
ServletConfig
1
2
web-fragment.xml
@WebServlet
当 servlet 容器完成上述所有初始化步骤时,ServletContextListener#contextInitialized()将使用一个ServletContextEvent参数调用 ,而该参数又包含所涉及的ServletContext. 这将使开发人员有机会以编程方式注册另一个Servlet,Filter或Listener.
ServletContextListener#contextInitialized()
ServletContextEvent
Filter
Listener
当 servlet 容器关闭时,它会卸载所有 Web 应用程序,调用其destroy()所有已初始化的 servlet 和过滤器的方法,并且通过 注册的所有Servlet,Filter和Listener实例ServletContext都被丢弃。最后ServletContextListener#contextDestroyed()将被调用,并且ServletContext本身将被丢弃。
destroy()
ServletContextListener#contextDestroyed()
HttpServletRequest
HttpServletResponse
servlet 容器连接到一个 Web 服务器,该服务器在某个端口号上侦听 HTTP 请求(通常在开发期间使用端口 8080,在生产中使用端口 80)。当客户端(例如,使用 Web 浏览器的用户,或以编程方式使用URLConnection发送 HTTP 请求时,servlet 容器会创建新对象HttpServletRequest和HttpServletResponse对象,并将它们传递给Filter链中定义的任何对象,最终传递给Servlet实例。
URLConnection
在过滤器的情况下,该doFilter()方法被调用。当 servlet 容器的代码调用chain.doFilter(request, response)时,请求和响应继续到下一个过滤器,如果没有剩余的过滤器,则点击 servlet。
doFilter()
chain.doFilter(request, response)
在servletservice()的情况下,将调用该方法。默认情况下,此方法doXxx()根据 request.getMethod(). 如果确定的方法在 servlet 中不存在,则在响应中返回 HTTP 405 错误。
service()
doXxx()
request.getMethod()
请求对象提供对有关 HTTP 请求的所有信息的访问,例如其URL、标头、查询字符串和正文。响应对象提供了以您想要的方式控制和发送 HTTP 响应的能力,例如,允许您设置标头和正文(通常使用从 JSP 文件生成的 HTML 内容)。当 HTTP 响应提交并完成时,请求和响应对象都会被回收并可供重用。
HttpSession
当客户端第一次访问 webapp 和/或第一次HttpSession通过记忆。servlet 容器还在HTTP 响应的标头中设置 a作为其名称,并将唯一会话 ID 作为其值。request.getSession()``HttpSession``session.getId()CookieSet-Cookie``JSESSIONID
request.getSession()``HttpSession``session.getId()
Cookie
Set-Cookie``JSESSIONID
根据HTTP cookie 规范(任何体面的 Web 浏览器和 Web 服务器必须遵守的合同),只要 cookie 有效,客户端(Web 浏览器)就需要在后续请求中将这个cookieCookie发送回标头中(即唯一 ID 必须引用未过期的会话并且域和路径正确)。使用浏览器的内置 HTTP 流量监视器,您可以验证 cookie 是否有效(在 Chrome / Firefox 23+ / IE9+ 中按 F12,然后检查 网络/网络 选项卡)。servlet 容器将检查Cookie每个传入 HTTP 请求的标头是否存在带有名称的 cookie,JSESSIONID并使用其值(会话 ID)HttpSession从服务器内存中获取相关信息。
JSESSIONID
保持活动状态,HttpSession直到它空闲(即未在请求中使用)超过 中指定的超时值,中<session- timeout>的设置web.xml。超时值默认为 30 分钟。因此,当客户端不访问 Web 应用程序的时间超过指定时间时,servlet 容器会丢弃会话。每个后续请求,即使指定了 cookie,也将无法再访问同一会话;servlet 容器将创建一个新会话。
<session- timeout>
在客户端,只要浏览器实例正在运行,会话 cookie 就会保持活动状态。因此,如果客户端关闭浏览器实例(所有选项卡/窗口),则会话在客户端被丢弃。在新的浏览器实例中,与会话关联的 cookie 将不存在,因此将不再发送。这会导致HttpSession创建一个全新的会话 cookie,并使用一个全新的会话 cookie。
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. } }