假设我有一个Web服务器,其中包含许多servlet.对于在这些servlet之间传递的信息,我正在设置会话和实例变量.
现在,如果两个或更多用户向该服务器发送请求,那么会话变量会发生什么变化
还有一个类似的问题,如果有n
个用户访问一个特定的servlet,那么这个servlet只会在第一个用户第一次访问它时被实例化,还是会分别为所有用户实例化
假设我有一个Web服务器,其中包含许多servlet.对于在这些servlet之间传递的信息,我正在设置会话和实例变量.
现在,如果两个或更多用户向该服务器发送请求,那么会话变量会发生什么变化
还有一个类似的问题,如果有n
个用户访问一个特定的servlet,那么这个servlet只会在第一个用户第一次访问它时被实例化,还是会分别为所有用户实例化
ServletContext
当servlet容器(如Apache Tomcat)启动时,它将部署并加载其所有web应用程序.加载web应用程序时,servlet容器会创建ServletContext
并将其保存在服务器的内存中.该web应用程序的web.xml
个文件以及所有包含的web-fragment.xml
个文件都将被解析,找到的每个<servlet>
、<filter>
和<listener>
(或分别用@WebServlet
、@WebFilter
和@WebListener
注释的每个类)将被实例化一次,并保存在服务器的内存中,通过ServletContext
注册.对于每个实例化的过滤器,它的init()
方法都会被一个新的FilterConfig
参数调用,该参数又包含所涉及的ServletContext
.
当Servlet
的<servlet><load-on-startup>
或@WebServlet(loadOnStartup)
值大于0
时,它的init()
方法也会在启动期间使用新的ServletConfig
参数调用,该参数又包含所涉及的ServletContext
.这些servlet按照该值指定的相同顺序初始化(1
是第一个,2
是第二个,等等).如果为多个servlet指定了相同的值,那么每个servlet的加载顺序与web.xml
、web-fragment.xml
或@WebServlet
类加载中的顺序相同.如果没有"启动时加载"值,那么每当HTTP request第一次点击该servlet时,就会调用init()
方法.
当servlet容器完成上述所有初始化步骤后,将使用ServletContextEvent
参数调用ServletContextListener#contextInitialized()
,该参数又包含所涉及的ServletContext
.这将使开发人员有机会以编程方式注册另一个Servlet
、Filter
或Listener
.
当servlet容器关闭时,它卸载所有web应用程序,调用其所有初始化servlet和过滤器的destroy()
方法,通过ServletContext
注册的所有Servlet
、Filter
和Listener
实例都被丢弃.最后,ServletContextListener#contextDestroyed()
将被调用,而ServletContext
本身将被丢弃.
HttpServletRequest
and HttpServletResponse
Servlet容器连接到Web服务器,该服务器侦听特定端口号上的HTTP请求(端口8080通常在开发期间使用,在生产中使用端口80).当客户端(例如,具有网络浏览器或programmatically using URLConnection
的用户)发送HTTP请求时,Servlet容器创建新的HttpServletRequest
和HttpServletResponse
对象,并将它们传递通过链中的任何定义的Filter
,并最终传递到Servlet
实例.
在filters的情况下,调用doFilter()
方法.当servlet容器的代码调用chain.doFilter(request, response)
时,请求和响应继续到下一个过滤器,如果没有剩余的过滤器,则点击servlet.
在servlets的情况下,调用service()
方法.默认情况下,此方法根据request.getMethod()
个方法确定要调用doXxx()
个方法中的哪个方法.如果所确定的方法不在Servlet中,则在响应中返回HTTP405错误.
Request对象提供对有关HTTP请求的所有信息的访问,例如其URL、headers、query string和正文.Response对象提供了以您想要的方式控制和发送HTTP响应的能力,例如,允许您设置头和正文(通常带有从JSP文件生成的HTML内容).当HTTP响应提交并完成时,请求和响应对象都将被回收并可供重用.
HttpSession
当客户端第一次访问webapp和/或第一次通过request.getSession()
获得HttpSession
时,servlet容器创建一个新的HttpSession
对象,生成一个长且唯一的ID(可以通过session.getId()
获得),并将其存储在服务器的内存中.servlet容器还在HTTP响应的Set-Cookie
报头中设置Cookie
,JSESSIONID
作为其名称,唯一会话ID作为其值.
根据HTTP cookie specification(任何像样的web浏览器和web服务器都必须遵守的合同),只要cookie有效(即,唯一ID必须引用未过期的会话并且域和路径正确),客户端(web浏览器)就需要在Cookie
报头中的后续请求中发回此cookie.使用浏览器内置的HTTP流量监控器,您可以验证cookie是否有效(在Chrome/Firefox 23+/IE9+中按F12,然后判断Net/Network选项卡).Servlet容器将判断每个传入HTTP请求的Cookie
头是否存在名为JSESSIONID
的cookie,并使用其值(会话ID)从服务器内存中获取关联的HttpSession
.
HttpSession
将保持活动状态,直到其空闲(即未在请求中使用)超过<session-timeout>
中指定的超时值(web.xml
中的设置).超时值默认为30分钟.因此,当客户机访问web应用的时间超过指定的时间时,servlet容器会丢弃session个应用程序.每个后续请求,即使指定了cookie,也将无法再访问同一会话;servlet容器将创建一个新会话.
在客户端,只要浏览器实例在运行,会话cookie就会保持活动状态.因此,如果客户端关闭浏览器实例(所有选项卡/窗口),则会话在客户端被 destruct .在新的浏览器实例中,与会话关联的cookie将不存在,因此它将不再被发送.这将创建一个全新的HttpSession
,并使用一个全新的会话cookie.
ServletContext
人的生命周期 与web应用的生命周期 一样长.它在all个会话中的all个请求中共享.HttpSession
就会一直存在.它在same会话中的all个请求之间共享.HttpServletRequest
和HttpServletResponse
从servlet从客户端接收HTTP请求开始,一直持续到完整响应(网页)到达为止.它在其他地方共享.Servlet
个、Filter
个和Listener
个实例的生命周期 与web应用的生命周期 一样长.它们在all个会话的all个请求中共享.ServletContext
、HttpServletRequest
和HttpSession
中定义的任何attribute
都将与所述对象的生命周期 一样长.对象本身表示bean管理框架(如JSF、CDI、Spring等)中的"作用域".这些框架将其作用域bean存储为其最接近匹配作用域的attribute
.也就是说,你主要关心的可能是thread safety.您现在应该知道,servlet和过滤器是在所有请求之间共享的.这就是Java的优点,它是多线程的,不同的线程(读:HTTP请求)可以使用同一个实例.否则,重新创建它们的成本太高,每一个请求都有init()
个和destroy()
个.
您还应该意识到,应该将任何请求或会话范围的数据分配为servlet或过滤器的instance变量.它将在其他会话中的所有其他请求中共享.这是not线程安全!下面的例子说明了这一点:
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.
}
}