假设我有一个包含多个servlet的Web服务器。为了在这些servlet之间传递信息,我设置了会话和实例变量。
现在,如果有2个或更多用户向该服务器发送请求,那么会话变量会发生什么情况呢?
它们对所有用户都是共同的还是每个用户都不同?
如果它们是不同的,那么服务器如何区分不同的用户?
再提一个类似的问题,如果有n
个用户访问特定的servlet,那么这个servlet只在第一个用户访问时被实例化,还是对所有用户分别被实例化?
换句话说,实例变量会发生什么情况?
假设我有一个包含多个servlet的Web服务器。为了在这些servlet之间传递信息,我设置了会话和实例变量。
现在,如果有2个或更多用户向该服务器发送请求,那么会话变量会发生什么情况呢?
它们对所有用户都是共同的还是每个用户都不同?
如果它们是不同的,那么服务器如何区分不同的用户?
再提一个类似的问题,如果有n
个用户访问特定的servlet,那么这个servlet只在第一个用户访问时被实例化,还是对所有用户分别被实例化?
换句话说,实例变量会发生什么情况?
ServletContext
ServletContext
的实例,并将其保存在服务器的内存中。Web应用程序的web.xml
和所有包含的web-fragment.xml
文件都会被解析,每个<servlet>
、<filter>
和<listener>
(或者每个使用@WebServlet
、@WebFilter
和@WebListener
注解的类)都会被实例化一次,并且也会保存在服务器的内存中,通过ServletContext
进行注册。对于每个实例化的过滤器,它的init()
方法会被调用,参数是一个FilterConfig
的实例,该实例包含了相关的ServletContext
。<servlet><load-on-startup>
或@WebServlet(loadOnStartup)
值为0
或更大时,它的init()
方法也会在启动期间被调用。这些Servlet按照指定的值的顺序进行初始化。如果为多个Servlet指定了相同的值,则每个Servlet都按照它们在web.xml
、web-fragment.xml
或@WebServlet
类加载中出现的顺序进行加载。如果"load-on-startup"值不存在或为负数,则init()
方法将在HTTP请求第一次命中该Servlet时被调用。有两个init()
方法,一个接受一个ServletConfig
实例作为参数,该实例包含相关的ServletContext
,另一个不接受任何参数,但可以通过继承的getServletContext()
方法获得ServletContext
。ServletContextListener#contextInitialized()
,并传入一个ServletContextEvent
参数,该参数包含了相关的ServletContext
。这将为开发人员提供机会,以编程方式注册另一个Servlet
、Filter
或Listener
。doFilter()
方法。当Servlet容器的代码调用chain.doFilter(request, response)
时,请求和响应会继续传递给下一个过滤器,如果没有剩余的过滤器,则传递给Servlet。service()
方法。默认情况下,该方法根据request.getMethod()
确定要调用的doXxx()
方法。如果Servlet中没有找到确定的方法,则会在响应中返回HTTP 405错误。HttpSession
Cookie
头部发送此cookie,只要cookie有效(即唯一ID必须指向未过期的会话,并且域名和路径正确)。通过浏览器内置的HTTP流量监视器,您可以验证cookie的有效性(在Chrome / Edge / Firefox 23+ / IE9+中按F12,然后检查Net/Network选项卡)。Servlet容器将检查每个传入的HTTP请求的Cookie
头部,以查看是否存在名为JSESSIONID
的cookie,并使用其值(会话ID)从服务器内存中获取关联的HttpSession
。
HttpSession
会一直保持活动状态,直到它在请求中没有被使用(即空闲)的时间超过了<session-timeout>
中指定的超时值,这是在web.xml
中的一个设置。默认的超时值取决于Servlet容器,通常为30分钟。因此,当客户端超过指定的时间不访问Web应用程序时,Servlet容器会销毁会话。即使在指定了cookie的情况下,每个后续请求也将无法再访问相同的会话;Servlet容器将创建一个新的会话。HttpSession
,并使用全新的会话cookie。ServletContext
的生命周期与Web应用程序的生命周期相同。它在所有会话中的所有请求之间共享。HttpSession
的生命周期与客户端与Web应用程序的交互以及会话在服务器端未超时的时间相同。它在同一会话中的所有请求之间共享。HttpServletRequest
和HttpServletResponse
的生命周期从Servlet接收到客户端的HTTP请求开始,直到完整的响应(网页)到达为止。它不在其他地方共享。Servlet
、Filter
和Listener
实例的生命周期与Web应用程序的生命周期相同。它们在所有会话中的所有请求之间共享。ServletContext
、HttpServletRequest
和HttpSession
中定义的任何attribute
将与所涉及的对象的生命周期相同。对象本身代表了诸如JSF、CDI、Spring等Bean管理框架中的"scope"。这些框架将其作用域化的Bean存储为其最匹配的作用域的attribute
。话虽如此,你可能最关心的是线程安全。你现在应该知道,servlets和filters是在所有请求之间共享的。这就是Java的好处,它是多线程的,不同的线程(即:HTTP请求)可以使用同一个实例。否则,为每个请求重新创建、init()
和destroy()
将会非常昂贵。
你还应该意识到,你绝对不应该将任何请求或会话作用域的数据分配为servlet或filter的实例变量。它将会在其他会话中的所有其他请求之间共享。这是不安全的!下面的例子说明了这一点:
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服务器在每个访问者的第一次访问时为其发放一个唯一标识符。访问者必须携带该标识符以便下次被识别。该标识符还允许服务器正确地将属于一个会话的对象与另一个会话的对象隔离开来。
如果load-on-startup是false:
为什么每个客户端都有一个实例不是一个好主意?想一想:你会为每个订单雇用一个披萨师傅吗?如果这样做,你很快就会破产。
然而,这种方法存在一定的风险。请记住:这个单一的人将所有订单信息都放在他的口袋里。因此,如果您没有在Servlet上注意线程安全性, 他可能会给某个客户错误的订单。
Java Servlet中的会话与其他语言(如PHP)中的会话相同,它是唯一的用户。服务器可以通过不同的方式来跟踪它,例如使用cookies、URL重写等等。这个Java文档在Java Servlet上下文中解释了它,并指出会话的维护方式完全由服务器设计者自行决定。规范仅规定必须将其维护为对于多个连接到服务器的用户而言是唯一的。请查看此Oracle文章获取更多有关您两个问题的信息。
编辑 这里有一个非常好的教程,介绍如何在Servlet内部使用会话,以及这是Sun关于Java Servlets的一章,介绍了它们是什么以及如何使用它们。通过这两篇文章,您应该能够回答所有问题。
ServletContext
对象。该对象具有零个、一个或多个会话对象,即会话对象的集合。每个会话都由某种标识符字符串标识,如在其他答案中所见的卡通图像。该标识符可通过cookie或URL重写在客户端上跟踪。每个会话对象都有其自己的变量。 - Basil Bourque当Servlet容器(例如Apache Tomcat)启动时,如果在容器控制台上出现错误或显示错误,则它将从web.xml文件中读取(每个应用程序只有一个)。否则,它将使用web.xml(因此被称为部署描述符)部署和加载所有Web应用程序。
在servlet实例化阶段,servlet实例已准备就绪,但由于缺少两个信息,它无法提供客户端请求:
1:上下文信息
2:初始配置信息
Servlet引擎创建servletConfig接口对象,将上述缺失的信息封装到其中 servlet引擎通过提供servletConfig对象引用作为参数调用servlet的init()。一旦init()完全执行,servlet就准备好提供客户端请求了。
A) 只发生一次(对于每个客户端请求,都会创建一个新线程) servlet的一个实例可以为任意数量的客户端请求提供服务,即在为一个客户端请求提供服务之后,服务器不会关闭。它会等待其他客户端请求,即利用servlet(内部servlet引擎创建线程)克服了CGI(为每个客户端请求创建一个新进程)的限制。
A) 每当HttpServletRequest对象上调用getSession()时
步骤1:对传入的会话ID评估请求对象。
步骤2:如果ID不可用,则创建一个全新的HttpSession对象并生成其相应的会话ID(即哈希表的ID)。将会话ID存储到httpservlet响应对象中,并将HttpSession对象的引用返回给servlet(doGet/doPost)。
步骤3:如果ID可用,则不会创建全新的会话对象,而是从请求对象中选择会话ID,并使用会话ID作为键在会话集合中进行搜索。
一旦搜索成功,会话ID将存储到HttpServletResponse中,并将现有的会话对象引用返回给UserDefinedServlet的doGet()或doPost()。
1)当控制权从servlet代码转移到客户端时,不要忘记会话对象由servlet容器即servlet引擎持有。
2)多线程留给servlet开发人员来实现,即处理客户端的多个请求,无需担心多线程代码。
当应用程序启动(部署在servlet容器上)或首次访问时(取决于负载平衡设置),就会创建一个servlet。 当servlet被实例化时,调用servlet的init()方法。 然后该servlet(仅有的一个实例)处理所有请求(通过多个线程调用其service()方法)。因此,在其中不建议使用任何同步策略,并且应避免使用servlet的实例变量。 当应用程序被卸载(servlet容器停止)时,将调用destroy()方法。
会话- Chris Thompson所说的内容。
实例化- 当容器接收到与servlet映射的第一个请求时,servlet将被实例化(除非在web.xml
中使用<load-on-startup>
元素配置了启动时加载servlet)。同一实例用于处理后续请求。
Servlet规范JSR-315明确定义了Web容器在服务(以及doGet、doPost、doPut等方法)方法中的行为(2.3.3.1多线程问题,第9页):
Servlet容器可以通过servlet的service方法并发发送请求。为处理这些请求,Servlet开发人员必须为服务方法中的多个线程提供充分的并发处理。
虽然不建议使用,但开发人员的另一个选择是实现SingleThreadModel接口,该接口要求容器保证在服务方法中同时只有一个请求线程。Servlet容器可以通过在Servlet上序列化请求或维护Servlet实例池来满足此要求。如果Servlet属于已被标记为可分发的Web应用程序,则容器可以在应用程序跨越的每个JVM中维护Servlet实例池。
对于没有实现SingleThreadModel接口的Servlet,如果已经使用synchronized关键字定义了服务方法(或类似于doGet或doPost这样的方法,这些方法被派发到HttpServlet抽象类的服务方法),则Servlet容器无法使用实例池方法,而必须通过它进行请求序列化。强烈建议在这些情况下开发人员不要对服务方法(或派发给它的方法)进行同步,因为这会对性能产生不利影响。
不是。 Servlets 不是线程安全的
这允许同时访问多个线程
如果您想将其作为线程安全的Servlet。, 您可以选择
实现SingleThreadInterface(i)
这是一个空接口,没有
方法
或者我们可以使用同步方法
我们可以通过在方法前面使用synchronized关键字来使整个服务方法同步
例子::
public Synchronized class service(ServletRequest request,ServletResponse response)throws ServletException,IOException
或者我们可以将代码块放在同步块中
示例:
Synchronized(Object)
{
----Instructions-----
}
我觉得同步块比整个方法更好
同步
从上面的解释可以清楚地看出,通过实现SingleThreadModel,Servlet容器可以确保Servlet的线程安全性。容器实现可以通过以下两种方式实现:
1)将请求序列化(排队)到单个实例 - 这类似于Servlet没有实现SingleThreadModel但同步服务/ doXXX方法;或者
2)创建一个实例池 - 这是更好的选择,也是Servlet启动/初始化工作/时间与托管Servlet的环境的限制参数(内存/ CPU时间)之间的权衡。
PHPSESSID
cookie的PHP,具有ASP.NET_SessionID
cookie的ASP.NET等等。这也是为什么像一些JSP / Servlet MVC框架自动执行的使用;jsessionid = xxx
进行URL重写被反对的原因之一。只需确保会话ID从未在网页中通过URL或其他方式公开,以使不知情的最终用户不受攻击。 - BalusC