1217

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

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

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

4

8 回答 8

1909

ServletContext

当 servlet 容器(如Apache Tomcat)启动时,它将部署并加载其所有 Web 应用程序。加载 Web 应用程序时,servlet 容器会创建ServletContext一次并将其保存在服务器的内存中。Web 应用程序web.xml和所有包含的web-fragment.xml文件都被解析,并且每个<servlet>,<filter>和找到的(或每个分别用,和<listener>注释的类)将被实例化一次并保存在服务器的内存中,通过. 对于每个实例化的过滤器,它的方法被一个新参数调用,该参数又包含所涉及的.@WebServlet@WebFilter@WebListenerServletContextinit()FilterConfigServletContext

当 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 容器完成上述所有初始化步骤后,ServletContextListener#contextInitialized()将使用一个ServletContextEvent参数调用 ,而该参数又包含所涉及的ServletContext. 这将使开发人员有机会以编程方式注册另一个Servlet,FilterListener.

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

HttpServletRequestHttpServletResponse

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

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

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

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

HttpSession

当客户端第一次访问 webapp 和/或第一次HttpSession通过记忆。servlet 容器还在HTTP 响应的标头中设置 a作为其名称,并将唯一会话 ID 作为其值。request.getSession()HttpSessionsession.getId()CookieSet-CookieJSESSIONID

根据HTTP cookie 规范(任何体面的 Web 浏览器和 Web 服务器必须遵守的合同),只要 cookie 有效,客户端(Web 浏览器)就需要在后续请求中将这个cookieCookie发送回标头中(即唯一 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,并使用一个全新的会话 cookie。

简而言之

  • ServletContext只要网络应用程序存在,它就会存在。它在所有会话中的所有请求之间共享。
  • HttpSession只要客户端使用相同的浏览器实例与 Web 应用程序交互,并且会话在服务器端没有超时,它们就会一直存在。它在同一会话中的所有请求之间共享。
  • 并且从 servlet 接收HttpServletRequestHttpServletResponse来自客户端的 HTTP 请求开始,直到完整的响应(网页)到达。它不在其他地方共享。
  • 只要 Web 应用程序存在,所有Servlet,Filter和实例就会存在。Listener它们在所有会话中的所有请求之间共享。
  • 任何attribute在 中定义的ServletContextHttpServletRequest并且HttpSession只要有问题的对象存在,就会存在。对象本身代表 bean 管理框架(如 JSF、CDI、Spring 等)中的“范围”。这些框架将其作用域 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.
    } 
}

也可以看看:

于 2010-06-24T02:41:44.627 回答
439

会话

在此处输入图像描述 在此处输入图像描述

简而言之:网络服务器在每个访问者第一次访问时都会向他发出一个唯一的标识符。访客必须带回该 ID,以便下次识别。此标识符还允许服务器正确地将一个会话拥有的对象与另一个会话拥有的对象隔离开来。

Servlet 实例化

如果load-on-startupfalse

在此处输入图像描述 在此处输入图像描述

如果load-on-startuptrue

在此处输入图像描述 在此处输入图像描述

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

在此处输入图像描述

为什么每个客户端拥有一个实例不是一个好主意?想一想:你会为每一份订单雇佣一个披萨店员吗?这样做,你很快就会倒闭。

不过,它带来了很小的风险。记住:这个人把所有的订单信息都放在口袋里:所以如果你不注意servlet 上的线程安全,他最终可能会向某个客户发出错误的订单。

于 2013-07-06T16:38:25.323 回答
42

Java servlet 中的会话与 PHP 等其他语言中的会话相同。它是用户独有的。服务器可以通过不同的方式来跟踪它,例如 cookie、url 重写等。这篇Java 文档文章在 Java servlet 的上下文中对其进行了解释,并指出究竟如何维护会话是留给服务器设计者的实现细节。该规范仅规定它必须在与服务器的多个连接中对用户保持唯一性。查看Oracle的这篇文章,了解有关您的这两个问题的更多信息。

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

于 2010-06-24T00:20:59.583 回答
33

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

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

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

Q) 在 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 容器(即 servlet 引擎)持有

2)多线程留给servlet开发人员实现,即处理客户端的多个请求,无需担心多线程代码

简写形式:

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

于 2012-02-22T12:54:18.117 回答
21

Sessions - 克里斯汤普森所说的。

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

于 2010-06-24T00:27:46.483 回答
13

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

servlet 容器可以通过 servlet 的 service 方法发送并发请求。为了处理请求,Servlet 开发人员必须为服务方法中的多个线程的并发处理做出足够的准备。

虽然不推荐,但开发者的替代方案是实现 SingleThreadModel 接口,该接口要求容器保证服务方法中一次只有一个请求线程。servlet 容器可以通过序列化 servlet 上的请求或维护 servlet 实例池来满足此要求。如果 servlet 是已标记为可分发的 Web 应用程序的一部分,则容器可以在每个 JVM 中维护一个 servlet 实例池,该应用程序分布在该应用程序上。

对于没有实现 SingleThreadModel 接口的 servlet,如果服务方法(或分派给 HttpServlet 抽象类的服务方法的 doGet 或 doPost 等方法)已经用 synchronized 关键字定义,则 servlet 容器不能使用实例池方式,但必须通过它序列化请求。强烈建议开发人员不要在这些情况下同步服务方法(或分派给它的方法),因为这会对性能产生不利影响

于 2014-03-05T11:41:57.033 回答
0

,Servlet不是线程安全的

这允许一次访问多个线程

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

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

方法

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

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

方法前面的关键字

例子::

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

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

例子::

Synchronized(Object)

{

----Instructions-----

}

我觉得 Synchronized block 比 make the whole 方法好

同步的

于 2018-04-14T15:32:40.793 回答
0

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

1) 将请求序列化(排队)到单个实例 - 这类似于未实现 SingleThreadModel 但同步服务/ doXXX 方法的 servlet;或者

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

于 2019-02-06T06:43:53.800 回答