关于Java:servlet是如何工作的?实例化、会话、共享变量和多线程

How do servlets work? Instantiation, sessions, shared variables and multithreading

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

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

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


服务上下文

当servlet容器(如ApacheTomcat)启动时,它将部署并加载所有Web应用程序。加载Web应用程序时,servlet容器只创建一次ServletContext,并将其保存在服务器的内存中。对web应用的web.xml文件进行解析,找到的每个(或每个类分别用@WebServlet@WebFilter@WebListener注释)都被实例化一次并保存在服务器内存中。对于每个实例化的过滤器,它的init()方法是用新的FilterConfig调用的。好的。

当servlet容器关闭时,它卸载所有Web应用程序,调用其所有初始化的servlet和过滤器的destroy()方法,并且所有ServletContextServletFilterListener实例都被丢弃。好的。

Servlet@WebServlet(loadOnStartup)值大于0时,其init()方法也会在启动新ServletConfig时调用。这些servlet以该值指定的相同顺序初始化(1为1,2为2,等等)。如果为多个servlet指定了相同的值,则按照它们在web.xml@WebServlet类加载中出现的顺序加载每个servlet。如果缺少"启动时加载"值,则每当HTTP请求第一次命中servlet时,将调用init()方法。好的。httpservletrequest和httpservletresponse

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

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

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

请求对象提供对HTTP请求的所有信息的访问,例如其URL、头、查询字符串和主体。响应对象提供了控制和发送HTTP响应的能力,例如,允许您设置头和主体(通常是通过JSP文件生成的HTML内容)。当提交和完成HTTP响应时,请求和响应对象都将被回收,并可供重用。好的。会话

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

根据HTTP cookie规范(任何体面的Web浏览器和Web服务器必须遵守的合同),只要cookie有效(即唯一ID必须指未过期的会话,并且域和路径正确),客户端(Web浏览器)就需要在随后的请求中在Cookie头中发送此cookie。使用浏览器的内置HTTP流量监视器,您可以验证cookie是否有效(在chrome/firefox 23+/ie9+中按F12,然后检查net/network选项卡)。servlet容器将检查每个传入HTTP请求的Cookie头,以确定是否存在名为JSESSIONID的cookie,并使用其值(会话ID)从服务器内存中获取相关的HttpSession。好的。

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

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

  • ServletContext的寿命与web应用程序的寿命一样长。它在所有会话中的所有请求之间共享。
  • 只要客户机与Web应用程序使用相同的浏览器实例进行交互,并且会话在服务器端没有超时,HttpSession就可以使用。它在同一会话中的所有请求之间共享。
  • 从servlet接收到客户机的HTTP请求时起,HttpServletRequestHttpServletResponse就一直存在,直到到达完整的响应(网页)。它不在其他地方共享。
  • 所有ServletFilterListener实例的寿命与web应用程序的寿命相同。它们在所有会话的所有请求中共享。
  • ServletContextHttpServletRequestHttpSession中定义的任何attribute将与所讨论的对象的寿命一样长。对象本身表示bean管理框架(如jsf、cdi、spring等)中的"作用域"。这些框架将其作用域bean存储为其最近匹配作用域的attribute

线程安全性

也就是说,您主要关心的可能是线程安全。您现在应该知道servlet和过滤器在所有请求中都是共享的。这是Java的好东西,它是多线程的,并且不同的线程(Read:HTTP请求)可以使用同一个实例。如果不是这样的话,就太贵了,每一个请求都需要重新创建init()destroy()。好的。

您还应该认识到,不应该将任何请求或会话范围的数据分配为servlet或过滤器的实例变量。它将在其他会话中与所有其他请求共享。这不是线程安全的!下面的示例说明了这一点:好的。

1
2
3
4
5
6
7
8
9
10
11
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模式中/和/*之间的差异
  • 伺服系统中的Doget和DoPost
  • servlet似乎同步处理多个并发浏览器请求
  • 为什么servlet不是线程安全的?

好啊。


会议

enter image description hereenter image description here

简言之:Web服务器在第一次访问时为每个访问者发布一个唯一的标识符。客人必须把身份证拿回来,以便下次能认出他来。这个标识符还允许服务器正确地将一个会话拥有的对象与另一个会话拥有的对象隔离开来。

servlet实例化

如果启动时加载为假:

enter image description hereenter image description here

如果启动时加载为真:

enter image description hereenter image description here

一旦他进入服务模式和Groove,同一个servlet将处理来自所有其他客户机的请求。

enter image description here

为什么每个客户机只有一个实例不是一个好主意?想想这个:你会为每次来的订单雇一个披萨工吗?这样做你很快就会失业。

但风险很小。记住:这个家伙把所有的订单信息都放在口袋里:所以如果你不小心servlet上的线程安全,他可能会把错误的订单给某个客户机。


JavaServlet中的会话与其他语言(如PHP)中的会话相同。它是用户独有的。服务器可以以不同的方式跟踪它,如cookies、URL重写等。JavaDoc文章在Java Servlet的上下文中解释它,并指出如何保持会话是服务器设计者留下的实现细节。该规范只规定,在与服务器的多个连接中,必须对用户保持唯一性。有关这两个问题的更多信息,请参阅Oracle的这篇文章。

编辑这里有一个关于如何在servlet内部使用会话的极好的教程。这里有一个关于JavaServlet的Sun的章节,它们是什么以及如何使用它们。在这两篇文章中,你应该能够回答你所有的问题。


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

在servlet的实例化阶段,servlet实例已准备就绪,但无法为客户机请求提供服务,因为它缺少两条信息:1:上下文信息2:初始配置信息

servlet引擎创建servletconfig接口对象,将上述缺少的信息封装到其中。servlet引擎通过提供servletconfig对象引用作为参数来调用servlet的init()。一旦init()被完全执行,servlet就可以为客户机请求提供服务了。

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

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

q)会议概念如何运作?

a)每当对httpServletRequest对象调用getSession()时

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

步骤2:如果ID不可用,则创建一个全新的httpsession对象,并生成相应的会话ID(即hashtable),会话ID存储到httpservlet响应对象中,并将httpsession对象的引用返回到servlet(doget/dopost)。

步骤3:如果ID可用,则不创建新的会话对象。从请求对象中提取会话ID。在会话集合中,使用会话ID作为键进行搜索。

一旦搜索成功,会话ID将存储到httpservletresponse中,并且现有的会话对象引用将返回到userdefineservlet的doget()或dopost()。

注:

1)当控制从servlet代码转移到客户机时,不要忘记会话对象正由servlet容器ie持有,servlet引擎

2)多线程留给servlet开发人员来实现,即处理客户机的多个请求,不必为多线程代码操心。

缩写形式:

servlet是在应用程序启动(部署在servlet容器上)或首次访问(取决于启动时加载设置)时创建的。当servlet被实例化时,会调用servlet的init()方法然后servlet(它的唯一实例)处理所有请求(它的service()方法由多个线程调用)。这就是为什么不建议在其中进行任何同步,并且应该避免servlet的实例变量。当应用程序未部署(servlet容器停止)时,将调用destroy()方法。


会议——克里斯·汤普森说的。

实例化-当容器收到映射到servlet的第一个请求时,会实例化servlet(除非servlet配置为在启动时加载web.xml中的元素)。同一实例用于服务后续请求。


servlet规范JSR-315清楚地定义了服务(以及doget、dopost、doput等)方法(2.3.3.1多线程问题,第9页)中的Web容器行为:

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


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

1)将请求(队列)序列化到单个实例-这类似于servlet,它不实现singlethreadmodel,而是同步服务/doxxx方法;或者

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


不,servlet不是线程安全的

这允许一次访问多个线程

如果你想让它作为线程安全的servlet,你可以

Implement SingleThreadInterface(i)这是一个空白的界面,没有

方法

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

我们可以使用synchronized使整个服务方法同步

方法前面的关键字

例子::

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

或者我们可以把代码块放在同步块中

例子::

1
2
3
4
5
6
7
Synchronized(Object)

{

----Instructions-----

}

我觉得同步块比整个方法好

同步的