java servlet是如何工作的?示例化、会话、共享变量和多线程

5lhxktic  于 2023-06-04  发布在  Java
关注(0)|答案(8)|浏览(365)

假设,我有一个Web服务器,其中包含许多servlet。为了在这些servlet之间传递信息,我设置了会话和示例变量。
现在,如果2个或更多的用户向这个服务器发送请求,那么会话变量会发生什么?
它们对所有用户都是通用的,还是对每个用户都是不同的?
如果它们是不同的,那么服务器如何能够区分不同的用户?
还有一个类似的问题,如果有n用户访问一个特定的servlet,那么这个servlet只在第一个用户第一次访问它时被示例化,还是为所有用户单独示例化?
换句话说,示例变量会发生什么变化?

r1zk6ea1

r1zk6ea11#

ServletContext

当servlet容器(如Apache Tomcat)启动时,它将部署和加载其所有Web应用程序。当加载Web应用程序时,servlet容器创建一次ServletContext的示例,并将其保存在服务器的内存中。Web应用程序的web.xml和所有包含的web-fragment.xml文件都将被解析,找到的每个<servlet><filter><listener>(或分别用@WebServlet@WebFilter@WebListener注解的每个类)将被示例化一次,并保存在服务器的内存中,通过ServletContext注册。对于每个示例化的过滤器,它的init()方法被调用,并使用FilterConfig的示例作为参数,该参数反过来包含所涉及的ServletContext
当一个Servlet<servlet><load-on-startup>@WebServlet(loadOnStartup)值大于0时,它的init()方法也会在启动过程中被调用,并使用ServletConfig的示例作为参数,该示例反过来包含所涉及的ServletContext。这些servlet以该值指定的相同顺序初始化(1是第一个,2是第二个,等等)。如果为多个servlet指定了相同的值,那么这些servlet中的每一个都将按照它们在web.xmlweb-fragment.xml@WebServlet类加载中出现的顺序进行加载。如果“load-on-startup”值不存在,则每当HTTP请求第一次命中该servlet时,就会调用init()方法。
当servlet容器完成上述所有初始化步骤时,ServletContextListener#contextInitialized()将被调用,并带有一个ServletContextEvent参数,该参数又包含所涉及的ServletContext。这将允许开发人员以编程方式注册另一个ServletFilterListener
当servlet容器关闭时,它会卸载所有Web应用程序,调用所有初始化的servlet和过滤器的destroy()方法,并且通过ServletContext注册的所有ServletFilterListener示例都将被丢弃。最后,ServletContextListener#contextDestroyed()将被调用,ServletContext本身将被丢弃。

HttpServletRequestHttpServletResponse

servlet容器连接到一个Web服务器,该服务器在某个端口号上侦听HTTP请求(通常在开发期间使用端口8080,在生产中使用端口80)。当客户端(例如当一个使用Web浏览器的用户(或programmatically using URLConnection)发送HTTP请求时,servlet容器创建HttpServletRequestHttpServletResponse的新示例,并将它们传递给链中任何已定义的Filter,最终传递给Servlet示例。
filters的情况下,将调用doFilter()方法。当servlet容器的代码调用chain.doFilter(request, response)时,请求和响应继续到下一个过滤器,或者如果没有剩余的过滤器,则命中servlet。
servlets的情况下,将调用service()方法。默认情况下,此方法根据request.getMethod()确定调用哪一个doXxx()方法。如果确定的方法不在servlet中,则在响应中返回HTTP 405错误。
请求对象提供了对HTTP请求的所有信息的访问,例如它的URLheadersquery string和主体。response对象提供了以您想要的方式控制和发送HTTP响应的能力,例如,允许您设置头和正文(通常使用从JSP文件生成的HTML内容)。当HTTP响应被提交并完成时,请求和响应对象都被回收并可供重用。

HttpSession

当客户端第一次访问webapp和/或通过request.getSession()第一次获得HttpSession时,servlet容器创建一个新的HttpSession对象,生成一个长且唯一的ID(可以通过session.getId()获得),并将其存储在服务器的内存中。servlet容器还在HTTP响应的Set-Cookie标头中设置Cookie,并将JSESSIONID作为其名称,将唯一会话ID作为其值。
根据HTTP cookie specification(任何像样的Web浏览器和Web服务器都必须遵守的合同),只要cookie有效(即,客户端(Web浏览器)就需要在Cookie头部的后续请求中发送此cookie。唯一ID必须指未过期的会话,并且域和路径是正确的)。使用浏览器内置的HTTP流量监视器,您可以验证cookie是否有效(在Chrome / Firefox 23+ / IE9+中按F12,并检查 Net/Network 选项卡)。servlet容器将检查每个传入HTTP请求的Cookie头,以确定是否存在名为JSESSIONID的cookie,并使用其值(会话ID)从服务器内存中获取关联的HttpSession

HttpSession保持活动状态,直到它空闲(即未在请求中使用)超过<session-timeout>中指定的超时值(web.xml中的设置)。超时值默认为30分钟。因此,当客户端没有访问Web应用程序的时间超过指定的时间时,servlet容器将丢弃会话。每个后续的请求,即使使用了指定的cookie,也将不再能够访问同一会话; servlet容器将创建新的会话。
在客户端,只要浏览器示例正在运行,会话cookie就会保持活动状态。因此,如果客户端关闭浏览器示例(所有选项卡/窗口),则会话在客户端侧被丢弃。在新的浏览器示例中,与会话关联的cookie将不存在,因此将不再发送。这将导致创建一个全新的HttpSession,并使用一个全新的会话cookie。

简单来说

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

线程安全

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

另请参阅:

fjaof16o

fjaof16o2#

会话

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

Servlet示例化

如果load-on-startupfalse

如果load-on-startuptrue

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

为什么每个客户端只有一个示例不是一个好主意?想想这个:你会为每一份订单雇一个送披萨的人吗?如果你这么做的话,你很快就会被炒鱿鱼。
不过,这也有一个小风险。记住:这个单身的家伙把所有的订单信息都装在口袋里:所以如果你对thread safety on servlets不谨慎,他可能最终会给某个客户错误的订单。

lf5gs5x2

lf5gs5x23#

Java servlet中的会话与PHP等其他语言中的会话相同。它对用户来说是唯一的。服务器可以通过不同的方式跟踪它,如cookie,URL重写等。这篇Java doc文章在Javaservlet的上下文中解释了它,并指出如何维护会话是留给服务器设计人员的实现细节。规范只规定它必须在与服务器的多个连接中对用户保持唯一。查看this article from Oracle以了解有关这两个问题的更多信息。

Edit有一个很好的教程here,介绍如何在servlet中使用session。而here是Sun的一章,关于Java Servlet,它们是什么以及如何使用它们。在这两篇文章之间,你应该能够回答你所有的问题。

kh212irz

kh212irz4#

当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)。
步骤3:如果ID可用,则未创建全新的会话对象,从请求对象中提取会话ID,以会话ID为关键字在会话集合中进行搜索。

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

注意:

1)当控制权从servlet代码转移到客户端时,不要忘记session对象是由servlet容器(即servlet引擎)持有的。
2)多线程留给servlet开发人员来实现,即处理客户端的多个请求,没有必要为多线程代码操心。

简称:

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

q3qa4bjr

q3qa4bjr5#

”””克里斯·汤普森说。

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

mkshixfv

mkshixfv6#

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,如果服务方法(或诸如doGet或doPost之类的方法,它们被分派到HttpServlet抽象类的服务方法)已经用synchronized关键字定义,则servlet容器不能使用示例池方法,而必须通过它序列化请求。强烈建议开发人员不要在这些情况下同步服务方法(或分派给它的方法),因为这会对性能产生不利影响

mccptt67

mccptt677#

否,Servlet不是线程安全

这允许一次访问多个线程

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

Implement SingleThreadInterface(i)这是一个空白的接口没有
方法
或者我们可以使用同步方法
我们可以通过使用synchronized使整个服务方法同步
方法前面的关键字

示例:

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

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

示例:

Synchronized(Object)

{

----Instructions-----

}

我觉得同步块比做整体的方法好
同步

xoshrz7s

xoshrz7s8#

从上面的解释可以清楚地看出,通过实现SingleThreadModel,servlet容器可以确保servlet的线程安全性。容器实现可以通过两种方式实现:
1)将请求序列化(排队)到单个示例-这类似于servlet不实现SingleThreadModel,但同步service/ doXXX方法;或
2)创建一个示例池--这是一个更好的选择,也是servlet的启动/初始化工作量/时间与托管servlet的环境的限制性参数(内存/ CPU时间)之间的折衷。

相关问题