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

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

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

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

答案

ServletContext

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

Servlet<servlet><load-on-startup>@WebServlet(loadOnStartup)值大于0 ,它的init()方法也会在启动期间使用新的ServletConfig调用。这些 servlet 以该值指定的相同顺序进行初始化( 1为 1st, 2为 2nd,依此类推)。如果为多个 servlet 指定了相同的值,则将按与出现在web.xmlweb-fragment.xml@WebServlet类加载中的顺序相同的顺序加载每个 servlet。如果没有 “启动时加载” 值,则当 HTTP 请求首次访问该 servlet 时,将调用init()方法。

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

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

HttpServletRequest 和 HttpServletResponse

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

对于filter ,将调用doFilter()方法。当 Servlet 容器的代码调用chain.doFilter(request, response) ,请求和响应将继续到下一个过滤器,如果没有剩余的过滤器,则单击 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()获取该 ID) session.getId() ),并将其存储在服务器的内存中。 servlet 容器还设置CookieSet-Cookie与 HTTP 响应的报头JSESSIONID正如它的名字和作为其值的唯一会话 ID。

根据HTTP cookie 规范 (任何体面的 Web 浏览器和 Web 服务器都必须遵守的合同),只要 cookie 有效,客户端(Web 浏览器)就需要在Cookie标头中的后续请求中将该 cookie 发送回去(即,唯一 ID 必须引用未到期的会话,并且域和路径正确)。使用浏览器的内置 HTTP 流量监控器,您可以验证 Cookie 是否有效(在 Chrome / Firefox 23 + / IE9 + 中按 F12,然后检查 “ 网络 / 网络”标签)。 Servlet 容器将检查每个传入 HTTP 请求的Cookie头是否存在名称为JSESSIONID的 cookie,并使用其值(会话 ID)从服务器内存中获取关联的HttpSession

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

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

简而言之

  • ServletContext生存时间与 Web 应用程序的生存时间相同。它在所有会话的所有请求之间共享。
  • 只要客户端与具有相同浏览器实例的 Web 应用程序进行交互,并且HttpSession在服务器端尚未超时, HttpSession存在。它在同一会话中的所有请求之间共享。
  • 从 Servlet 接收到来自客户端的 HTTP 请求开始,直到完整的响应(网页)到达为止, HttpServletRequestHttpServletResponse一直存在。它没有在其他地方共享。
  • 只要 Web 应用程序存在,所有ServletFilterListener实例都存在。它们在所有会话的所有请求之间共享。
  • 只要有问题的对象存在,在ServletContextHttpServletRequestHttpSession定义的任何attribute都将有效。对象本身代表了诸如 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.
    } 
}

也可以看看:

届会

在此处输入图片说明在此处输入图片说明

简而言之:Web 服务器在每个访问者 首次访问时都会为其分配一个唯一的标识符。访客必须带回该 ID,以便下次被识别。该标识符还允许服务器将一个会话所拥有的对象与另一个会话所拥有的对象正确地隔离开。

Servlet 实例化

如果启动时加载false

在此处输入图片说明在此处输入图片说明

如果启动时加载true

在此处输入图片说明在此处输入图片说明

一旦他进入了服务模式并参与了工作, 同一个 servlet 将处理来自所有其他客户端的请求。

在此处输入图片说明

每个客户端一个实例为什么不是一个好主意?考虑一下:您会为每份订单雇用一个披萨人吗?这样做,您很快就会破产。

不过,它带来的风险很小。记住:这个单身汉将所有订单信息都放在口袋里:因此,如果您对 servlet 的线程安全性不太谨慎,他可能最终会给某个客户端错误的订单。

Java Servlet 中的会话与其他语言(例如 PHP)中的会话相同。它是用户唯一的。服务器可以通过不同的方式来跟踪它,例如 cookie,URL 重写等。此Java 文档文章在 Java servlet 的上下文中对其进行了解释,并指出会话的确切维护方式是服务器设计者的实现细节。该规范仅规定,必须在与服务器的多个连接上将其维护为对用户唯一。 从 Oracle查阅本文,以获取有关这两个问题的更多信息。

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

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

在 Servlet 的实例化阶段,Servlet 实例已准备就绪,但由于缺少以下两条信息,因此无法满足客户端请求:
1:上下文信息
2:初始配置信息

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

问:在 Servlet 的生命周期中,实例化和初始化发生了多少次?

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

问)会议概念如何工作?

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

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

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

步骤 3 :如果未创建 ID 可用的全新会话对象,则使用会话 ID 作为关键字,从请求对象中检索会话 ID,以在会话集合中进行搜索。

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

注意:

1)当控制权从 servlet 代码移到客户端时,不要忘记会话对象被 servlet 容器即 servlet 引擎持有

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

简写形式:

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

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

实例化 - 当容器接收到映射到该 servlet 的第一个请求时,将实例化一个 servlet(除非 servlet 被配置为使用web.xml<load-on-startup>元素在<load-on-startup> )。相同的实例用于处理后续请求。

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

Servlet 容器可以通过 Servlet 的服务方法发送并发请求。为了处理请求,Servlet 开发人员必须为服务方法中的多线程并发处理做好充分准备。

尽管不建议这样做,但开发人员的另一种方法是实现 SingleThreadModel 接口,该接口要求容器保证服务方法中一次仅存在一个请求线程。 servlet 容器可以通过序列化 servlet 上的请求或维护 servlet 实例池来满足此要求。如果 Servlet 是已标记为可分发的 Web 应用程序的一部分,则容器可以在应用程序所分布的每个 JVM 中维护一个 Servlet 实例池。

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

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

1)将请求序列化(排队)到单个实例 - 这类似于未实现 SingleThreadModel 的 servlet,但是同步 service / doXXX 方法;要么

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

。Servlet 不是线程安全的

这允许一次访问多个线程

如果您想使其 Servlet 作为线程安全。,您可以选择

Implement SingleThreadInterface(i)这是一个空白接口,没有

方法

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

我们可以通过使用同步使整个服务方法成为同步的

方法前面的关键字

例::

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

或者我们可以将代码放入同步块中

例::

Synchronized(Object)

{

----Instructions-----

}

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

已同步