tangguo

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

java

假设我有一个Web服务器,其中包含许多Servlet。为了在这些servlet之间传递信息,我正在设置会话和实例变量。

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

还有一个类似的问题,如果有n用户正在访问特定的servlet,那么仅当第一个用户第一次访问该servlet时才实例化该servlet,还是单独为所有用户实例化该servlet?
换句话说,实例变量将如何处理?


阅读 292

收藏
2020-10-16

共1个答案

小编典典

ServletContext

当servlet容器(例如ApacheTomcat)启动时,它将部署并加载所有Web应用程序。加载Web应用程序后,Servlet容器将创建ServletContext一次并将其保存在服务器的内存中。Web应用程序的web.xml所有包含的web-fragment.xml文件进行解析,每个<servlet><filter><listener>发现(或每一类注释用@WebServlet,@WebFilter并@WebListener分别)被实例化一次,并保存在服务器的内存。对于每个实例化的过滤器,其init()方法均通过new调用FilterConfig。

当a的Servleta<servlet>``<load-on-startup>@WebServlet(loadOnStartup)value大于时0,则init()在启动期间还会使用new调用其方法ServletConfig。这些servlet以该值指定的相同顺序进行初始化(1为1st,2为2nd等)。如果多于一个的servlet指定了相同的值,则每个这些小服务程序的如它们出现在加载以相同的顺序web.xml,web-fragment.xml或@WebServlet类加载。如果不存在“启动时加载”值,则init()当HTTP请求首次访问该servlet时,将调用该方法。

当Servlet容器完成上述所有初始化步骤后,ServletContextListener#contextInitialized()将调用。

当servlet容器关闭时,它卸载所有Web应用程序,调用destroy()其全部初始化servlet和过滤器,所有的方法ServletContext,Servlet,Filter和Listener实例丢弃。最后,ServletContextListener#contextDestroyed()将被调用。

HttpServletRequest和HttpServletResponse

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

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

对于servlet,将service()调用该方法。默认情况下,此方法根据来确定doXxx()要调用的方法之一 request.getMethod()。如果servlet中没有确定的方法,则在响应中返回HTTP 405错误。

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

HttpSession

当客户端首次访问该Web应用程序和/或通过首次访问该应用程序HttpSessionrequest.getSession()Servlet容器将创建一个新HttpSession对象,生成一个长而唯一的ID(您可以通过获取session.getId()),并将其存储在服务器的记忆。Servlet容器还在HTTP响应Cookie的Set-Cookie标头中设置aJSESSIONID作为其名称,并将唯一会话ID作为其值。

根据HTTP cookie规范(任何体面的Web浏览器和Web服务器都必须遵守的合同),Cookie只要cookie有效,客户端(Web浏览器)就需要在标头的后续请求中将该cookie发送回去(也就是说,唯一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。

简而言之

  • ServletContext生活,只要Web应用程序的生命。它在所有会话的所有请求之间共享。
    HttpSession生活,只要客户端与同一个浏览器实例中的Web应用程序进行交互,和会话未在服务器端超时。它在同一会话中的所有请求之间共享。
  • HttpServletRequestHttpServletResponse现场从servlet接收来自客户端的HTTP请求的时间,直到完全缓解(网页)已经到来。它没有在其他地方共享。
  • 只要Web应用程序存在Servlet,所有FilterListener实例都会存在。它们在所有会话的所有请求之间共享。
  • 任何attribute被定义ServletContextHttpServletRequest并且HttpSession只要将生活中的问题生活中的对象。对象本身代表了诸如JSF,CDI,Spring等之类的bean管理框架中的“作用域”。这些框架将其范围内的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.
    } 
}
2020-10-16