假设,我有一个包含大量servlet的web服务器。对于在这些servlet之间传递的信息,我设置了会话和实例变量。

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

还有一个类似的问题,如果有n个用户访问一个特定的servlet,那么这个servlet只在第一个用户第一次访问它时实例化,还是为所有用户分别实例化? 换句话说,实例变量会发生什么变化?


Session in Java servlets is the same as session in other languages such as PHP. It is unique to the user. The server can keep track of it in different ways such as cookies, url rewriting etc. This Java doc article explains it in the context of Java servlets and indicates that exactly how session is maintained is an implementation detail left to the designers of the server. The specification only stipulates that it must be maintained as unique to a user across multiple connections to the server. Check out this article from Oracle for more information about both of your questions.

这里有一个关于如何在servlet中使用session的优秀教程。这里有来自Sun的一章关于Java servlet,它们是什么以及如何使用它们。在这两篇文章之间,你应该能够回答你所有的问题。


塞申斯是克里斯·汤普森说的。

实例化——当容器接收到第一个映射到servlet的请求时,servlet被实例化(除非servlet被配置为在web.xml中使用<load-on-startup>元素在启动时加载)。使用相同的实例为后续请求提供服务。


ServletContext

When the servlet container (like Apache Tomcat) starts up, it will deploy and load all its web applications. When a web application is loaded, the servlet container creates the ServletContext once and keeps it in the server's memory. The web app's web.xml and all of included web-fragment.xml files is parsed, and each <servlet>, <filter> and <listener> found (or each class annotated with @WebServlet, @WebFilter and @WebListener respectively) will be instantiated once and be kept in the server's memory as well, registred via the ServletContext. For each instantiated filter, its init() method is invoked with a new FilterConfig argument which in turn contains the involved ServletContext.

When a Servlet has a <servlet><load-on-startup> or @WebServlet(loadOnStartup) value greater than 0, then its init() method is also invoked during startup with a new ServletConfig argument which in turn contains the involved ServletContext. Those servlets are initialized in the same order specified by that value (1 is 1st, 2 is 2nd, etc). If the same value is specified for more than one servlet, then each of those servlets is loaded in the same order as they appear in the web.xml, web-fragment.xml, or @WebServlet classloading. In the event the "load-on-startup" value is absent, the init() method will be invoked whenever the HTTP request hits that servlet for the very first time.

当servlet容器完成上述所有初始化步骤时,将使用ServletContextListener#contextInitialized()调用servletconttevent参数,该参数反过来包含所涉及的ServletContext。这将允许开发人员有机会以编程方式注册另一个Servlet、过滤器或侦听器。

当servlet容器关闭时,它卸载所有web应用程序,调用所有已初始化的servlet和过滤器的destroy()方法,所有通过ServletContext注册的servlet、Filter和Listener实例都被丢弃。最后,将调用ServletContextListener#contextDestroyed(),并销毁ServletContext本身。

HttpServletRequest和HttpServletResponse

servlet容器附加到一个web服务器上,该服务器在某个端口号上侦听HTTP请求(开发期间通常使用8080端口,生产中使用80端口)。当客户端(例如使用web浏览器的用户,或以编程方式使用URLConnection的用户)发送一个HTTP请求时,servlet容器会创建新的HttpServletRequest和HttpServletResponse对象,并将它们通过链中任何定义的Filter传递,最终传递给servlet实例。

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

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

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

HttpSession

当客户端第一次访问web应用程序和/或第一次通过request.getSession()获得HttpSession时,servlet容器创建一个新的HttpSession对象,生成一个长而唯一的ID(你可以通过session.getId()获得),并将其存储在服务器的内存中。servlet容器还在HTTP响应的Set-Cookie报头中设置了一个Cookie,其名称为JSESSIONID,值为唯一的会话ID。

As per the HTTP cookie specification (a contract any decent web browser and web server must adhere to), the client (the web browser) is required to send this cookie back in subsequent requests in the Cookie header for as long as the cookie is valid (i.e. the unique ID must refer to an unexpired session and the domain and path are correct). Using your browser's built-in HTTP traffic monitor, you can verify that the cookie is valid (press F12 in Chrome / Firefox 23+ / IE9+, and check the Net/Network tab). The servlet container will check the Cookie header of every incoming HTTP request for the presence of the cookie with the name JSESSIONID and use its value (the session ID) to get the associated HttpSession from server's memory.

HttpSession保持活动状态,直到它空闲(即没有在请求中使用)超过<session-timeout> (web.xml中的设置)中指定的超时值。超时时间默认为30分钟。因此,当客户端访问web应用程序的时间超过指定的时间时,servlet容器将丢弃会话。每个后续请求,即使指定了cookie,也不会再访问同一个会话;servlet容器将创建一个新的会话。

在客户端,只要浏览器实例在运行,会话cookie就保持活动状态。因此,如果客户端关闭了浏览器实例(所有选项卡/窗口),那么会话在客户端被丢弃。在新的浏览器实例中,与会话关联的cookie将不存在,因此它将不再被发送。这将导致创建一个全新的HttpSession,并使用一个全新的会话cookie。

简单地说

The ServletContext lives for as long as the web app lives. It is shared among all requests in all sessions. The HttpSession lives for as long as the client is interacting with the web app with the same browser instance, and the session hasn't timed out at the server side. It is shared among all requests in the same session. The HttpServletRequest and HttpServletResponse live from the time the servlet receives an HTTP request from the client, until the complete response (the web page) has arrived. It is not shared elsewhere. All Servlet, Filter and Listener instances live as long as the web app lives. They are shared among all requests in all sessions. Any attribute that is defined in ServletContext, HttpServletRequest and HttpSession will live as long as the object in question lives. The object itself represents the "scope" in bean management frameworks such as JSF, CDI, Spring, etc. Those frameworks store their scoped beans as an attribute of its closest matching scope.

线程安全

也就是说,您主要关心的可能是线程安全。现在您应该知道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.
    } 
}

参见:

JSF、Servlet和JSP之间的区别是什么? Java会话管理的最佳选择 servlet映射url模式中/和/*的区别 servlet中的doGet和doPost Servlet似乎可以同步处理多个并发浏览器请求 为什么servlet不是线程安全的?


当servlet容器(如Apache Tomcat)启动时,如果出现任何错误或在容器端控制台显示错误,它将从web.xml文件中读取(每个应用程序只有一个),否则,它将使用web.xml(因此将其命名为部署描述符)部署和加载所有web应用程序。

在servlet实例化阶段,servlet实例已经准备好了,但是它不能为客户端请求服务,因为它缺少两个信息: 1:上下文信息 2:初始配置信息

Servlet引擎创建servletConfig接口对象,其中封装了上述缺失的信息 servlet引擎通过提供servletConfig对象引用作为参数来调用servlet的init()。一旦init()完全执行,servlet就准备好为客户端请求服务了。

Q)在servlet的生命周期中,实例化和初始化发生了多少次?

A)只有一次(对于每个客户端请求都创建一个新线程) 只有一个servlet实例服务于任意数量的客户端请求,即服务一个客户端请求后服务器不会死亡。它等待其他客户端请求,即CGI(每个客户端请求都会创建一个新进程)的限制被servlet(内部servlet引擎创建线程)克服。

Q)会话概念是如何工作的?

A)每当在HttpServletRequest对象上调用getSession()时

步骤1:评估请求对象的传入会话ID。

第二步:如果ID不可用,则创建一个全新的HttpSession对象,并生成相应的会话ID(即HashTable)。会话ID存储在httpservlet响应对象中,HttpSession对象的引用返回给servlet (doGet/doPost)。

第三步:如果ID可用,没有创建全新的会话对象,则从请求对象中获取会话ID,以会话ID为键在会话集合中进行搜索。

一旦搜索成功,会话ID将存储到HttpServletResponse中,现有的会话对象引用将返回到UserDefineservlet的doGet()或doPost()。

注意:

1)当控制从servlet代码转移到客户端时,不要忘记会话对象是由servlet容器(即servlet引擎)保存的

2)多线程留给servlet开发人员去实现ie。,处理客户端的多个请求,不用担心多线程代码

简称:

A servlet is created when the application starts (it is deployed on the servlet container) or when it is first accessed (depending on the load-on-startup setting) when the servlet is instantiated, the init() method of the servlet is called then the servlet (its one and only instance) handles all requests (its service() method being called by multiple threads). That's why it is not advisable to have any synchronization in it, and you should avoid instance variables of the servlet when the application is undeployed (the servlet container stops), the destroy() method is called.


会话

简而言之:web服务器在每个访问者第一次访问时向其发出唯一标识符。访客必须带回来这个ID,以便下次来的时候能被认出来。这个标识符还允许服务器正确地将一个会话拥有的对象与另一个会话拥有的对象隔离。

Servlet实例化

如果load-on-startup为false:

如果load-on-startup为true:

一旦他进入服务模式并处于最佳状态,同一个servlet将处理来自所有其他客户机的请求。

为什么每个客户端有一个实例不是一个好主意?想想看:你会为每一份披萨订单雇一个送披萨的吗?那样做的话,你很快就会破产。

但这也有一个小风险。记住:这个家伙把所有的订单信息都放在他的口袋里:因此,如果您不注意servlet上的线程安全,他可能最终会向某个客户端发出错误的订单。


Servlet规范JSR-315明确定义了服务(以及doGet、doPost、doPut等)方法中的web容器行为(2.3.3.1多线程问题,第9页):

A servlet container may send concurrent requests through the service method of the servlet. To handle the requests, the Servlet Developer must make adequate provisions for concurrent processing with multiple threads in the service method. Although it is not recommended, an alternative for the Developer is to implement the SingleThreadModel interface which requires the container to guarantee that there is only one request thread at a time in the service method. A servlet container may satisfy this requirement by serializing requests on a servlet, or by maintaining a pool of servlet instances. If the servlet is part of a Web application that has been marked as distributable, the container may maintain a pool of servlet instances in each JVM that the application is distributed across. For servlets not implementing the SingleThreadModel interface, if the service method (or methods such as doGet or doPost which are dispatched to the service method of the HttpServlet abstract class) has been defined with the synchronized keyword, the servlet container cannot use the instance pool approach, but must serialize requests through it. It is strongly recommended that Developers not synchronize the service method (or methods dispatched to it) in these circumstances because of detrimental effects on performance


不。servlet不是线程安全的

这允许一次访问多个线程

如果你想让它成为线程安全的Servlet,你可以去

实现SingleThreadInterface(我) 哪个是空白界面有没有

方法

或者我们可以使用同步方法

利用synchronized可以使整个服务方法实现同步化

关键字前面的方法

例如::

public Synchronized class service(ServletRequest request,ServletResponse response)throws ServletException,IOException

或者我们可以把代码的put块放在Synchronized块中

例如::

Synchronized(Object)

{

----Instructions-----

}

我觉得Synchronized块比整个方法更好

同步


从上面的解释可以清楚地看出,通过实现SingleThreadModel, servlet容器可以确保servlet的线程安全。容器实现可以通过两种方式做到这一点:

1)序列化请求(排队)到单个实例——这类似于servlet不实现SingleThreadModel,但同步service/ doXXX方法;或

2)创建一个实例池——这是一个更好的选择,可以在servlet的启动/初始化工作/时间与托管servlet的环境的限制性参数(内存/ CPU时间)之间进行权衡。