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


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

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

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


992
2018-06-24 00:16


起源




答案:


ServletContext中

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

当servlet容器关闭时,它会卸载所有Web应用程序,调用 destroy() 所有初始化的servlet和过滤器的方法,以及所有 ServletContextServletFilter 和 Listener 实例被破坏了。

当一个 Servlet 有一个 <servlet><load-on-startup> 要么 @WebServlet(loadOnStartup) 值大于 0, 它的 init() 在启动期间使用new调用方法 ServletConfig。这些servlet按照该值指定的相同顺序进行初始化(1 - > 1st,2 - > 2nd等)。如果为多个servlet指定了相同的值,则每个servlet按照它们出现的顺序加载 web.xml, 要么 @WebServlet 类加载。如果没有“load-on-startup”值,则 init() 只要HTTP请求第一次访问该servlet,就会调用该方法。

HttpServletRequest和HttpServletResponse

servlet容器连接到Web服务器,该服务器侦听特定端口号上的HTTP请求(端口8080通常在开发期间使用,端口80在生产中使用)。当客户端(具有Web浏览器的用户)发送HTTP请求时,servlet容器会创建新的 HttpServletRequest 和 HttpServletResponse 对象并将它们传递给任何定义的 Filter 链,最终, Servlet 实例。

如果是 过滤器doFilter() 方法被调用。当它的代码调用时 chain.doFilter(request, response),请求和响应继续到下一个过滤器,或者如果没有剩余过滤器则命中servlet。

如果是 小服务程序service() 方法被调用。默认情况下,此方法确定哪一个 doXxx() 基于的调用方法 request.getMethod()。如果servlet中没有确定的方法,则在响应中返回HTTP 405错误。

请求对象提供对HTTP请求的所有信息的访问,例如其标题和正文。响应对象提供了以您希望的方式控制和发送HTTP响应的功能,例如,允许您设置标头和正文(通常使用JSP文件中生成的HTML内容)。提交并完成HTTP响应后,请求和响应对象都将被回收并重新使用。

HttpSession中

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

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

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

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

简而言之

  • ServletContext 只要网络应用程序存在,它就会存在。它是共享的 所有 请求 所有 会话。
  • HttpSession 只要客户端使用相同的浏览器实例与Web应用程序进行交互,并且会话在服务器端没有超时,就会存在。它是共享的 所有 请求 相同 会话。
  • HttpServletRequest 和 HttpServletResponse 从servlet收到来自客户端的HTTP请求开始直到完成响应(网页)到达为止。它是  在别处分享。
  • 所有 ServletFilter 和 Listener 只要Web应用程序存在,实例就会存在。它们是共享的 所有 请求 所有 会话。
  • 任何 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.
    } 
}

也可以看看:


1625
2018-06-24 02:41



所以,当我以某种方式找到发送给客户端的JSessionId时,我可以窃取他的会话? - Toskan
@Toskan:那是对的。它被称为 会话固定黑客。请注意,这不是JSP / Servlet特有的。通过cookie维护会话的所有其他服务器端语言也是敏感的,就像PHP一样 PHPSESSID cookie,ASP.NET用 ASP.NET_SessionID cookie,etcetera。这也是URL重写的原因 ;jsessionid=xxx 正如一些JSP / Servlet MVC框架自动做的那样令人不悦。只需确保会话ID永远不会在URL中或通过网页中的其他方式公开,以便不会攻击不知不觉的最终用户。 - BalusC
@Toskan:另外,请确保您的webapp对XSS攻击不敏感。即不要以非转义形式重新显示任何用户控制的输入。 XSS打开了通往收集所有最终用户的会话ID的方法。也可以看看 XSS背后的一般概念是什么? - BalusC
@BalusC,对不起我的愚蠢。这意味着所有用户都访问thisIsNOTThreadSafe的同一个实例吗? - overshadow
@overshadow是的,每个用户都可以访问同一个实例 - Harry


会议

enter image description here enter image description here

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

Servlet实例化

如果 时加载的启动 是

enter image description here enter image description here

如果 时加载的启动 是 真正

enter image description here enter image description here

一旦他处于服务模式和沟槽上,就可以了 相同 servlet将处理来自所有其他客户端的请求。

enter image description here

为什么每个客户端都有一个实例不是一个好主意?想一想:你会为每一个订单雇用一个披萨店吗?这样做,你很快就会破产。

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


395
2017-07-06 16:38



你的照片非常适合我的理解。我有一个问题,这个披萨餐厅会在太多披萨订单来的时候做什么,只等一个披萨店或雇用更多披萨店的人?谢谢 。 - zh18
他会回复一条消息 to many requests at this moment. try again later - PSyLoCKe


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

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


40
2018-06-24 00:20



这给我带来了另一个问题,因为整个应用程序只有一个servlet上下文,我们可以通过这个servletcontext访问会话变量,那么会话变量如何才能对每个用户都是唯一的呢?谢谢.. - Ku Jon
你是如何从servletContext访问会话的?你不是指servletContext.setAttribute(),不是吗? - matt b
哦,伙计...... request.getSession()! RTFM! - Vladimir Dyuzhev
@KuJon每个网络应用程序都有一个 ServletContext 目的。该对象具有零个,一个或多个会话对象 - 会话对象的集合。每个会话都由某种标识符字符串标识,如其他答案中的漫画所示。通过cookie或URL重写在客户端上跟踪该标识符。每个会话对象都有自己的变量。 - Basil Bourque


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

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

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

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

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

问)会话概念如何运作?

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

步骤1:请求对象是传入会话ID的evalauated。

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

第3步:如果未创建ID可用的全新会话对象,则从请求对象中拾取会话ID,并使用会话ID作为密钥在会话集合中进行搜索。

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

注意:

1)当控件从servlet代码离开到客户端时不要忘记servletcontainer正在保存会话对象,即servletengine

2)多线程留给servlet devlopers人们实现ie。,处理客户端的多个请求无需担心多线程代码

简短形式:

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


30
2018-02-22 12:54





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

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


20
2018-06-24 00:27



正确。额外的想法:每个请求获得一个新的(或循环的)线程在该单个Servlet实例上运行。每个Servlet都有一个实例,可能还有很多线程(如果同时有多个请求)。 - Basil Bourque


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

servlet容器可以通过服务发送并发请求   servlet的方法。要处理请求,请使用Servlet Developer   必须为多个并发处理做出充分准备   服务方法中的线程。

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

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


13
2018-03-05 11:41



仅供参考,当前的Servlet规范(2015-01)为3.1,定义为 JSR 340。 - Basil Bourque
JSR 340:Java Servlet 3.1规范 和 JSR 369:JavaTM Servlet 4.0规范 - Reva
非常整洁的答案! @tharindu_DG - rm -rf


没有。 Servlets是 不是线程安全

允许一次访问多个线程

如果你想把它作为Thread安全的Servlet,你可以去

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

方法

或者我们可以去同步方法

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

方法面前的keword

例::

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

或者我们可以在Synchronized块中放置代码块

例::

Synchronized(Object)

{

----Instructions-----

}

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

同步


0
2018-04-14 15:32