假设,我有一个包含许多servlet的Web服务器。为了在这些servlet之间传递信息,我设置了会话和实例变量。
现在,如果2个或更多用户向该服务器发送请求,那么会话变量会发生什么?它们对所有用户都是通用的,还是对每个用户都是不同的?如果它们不同,那么服务器如何区分不同的用户?
还有一个类似的问题,如果有n个用户访问特定的servlet,那么这个servlet只在第一个用户第一次访问它时被实例化,还是它单独为所有用户实例化?换句话说,实例变量会发生什么?
n
servlet中Java会话与PHP等其他语言中的会话相同。它对用户是唯一的。服务器可以通过不同的方式跟踪它,例如cookie、url重写等。这篇Java医生文章在Javaservlet的上下文中解释了它,并指出究竟如何维护会话是留给服务器设计者的实现细节。规范只规定它必须在与服务器的多个连接中对用户保持唯一。查看本文来自Oracle以获取有关您两个问题的更多信息。
编辑有一个关于如何在servlet中使用会话的优秀教程这里。这里是Sun关于JavaServlet的一章,它们是什么以及如何使用它们。在这两篇文章之间,您应该能够回答所有问题。
#30340;克里斯·汤普森说过的话。
实例化-当容器接收到映射到servlet的第一个请求时,servlet被实例化(除非servlet配置为在启动时使用web.xml中的<load-on-startup>元素加载)。相同的实例用于为后续请求提供服务。
web.xml
<load-on-startup>
ServletContext
当servlet容器(如web.xml3)启动时,它将部署并加载其所有的Web应用程序。当Web应用程序加载时,servlet容器创建一次web.xml4并将其保存在服务器的内存中。Web应用程序的web.xml和所有包含的web-fragment.xml文件被解析,找到的每个<servlet>、<filter>和<listener>(或分别用@WebServlet、@WebFilter和@WebListener注释的每个类)将被实例化一次,并保存在服务器的内存中,通过ServletContext注册。对于每个实例化的过滤器,它的web.xml0方法会用一个新的web.xml5参数调用,该参数反过来包含相关的ServletContext。
web-fragment.xml
<servlet>
<filter>
<listener>
@WebServlet
@WebFilter
@WebListener
当Servlet的<servlet><load-on-startup>或@WebServlet(loadOnStartup)值大于0时,它的init()方法也会在启动过程中使用一个新的<servlet><load-on-startup>3参数调用,该参数反过来包含所涉及的ServletContext。这些servlet按照该值指定的相同顺序初始化(1是第一个,2是第二个,等等)。如果为多个servlet指定了相同的值,那么每个servlet的加载顺序都与它们在web.xml、<servlet><load-on-startup>0或<servlet><load-on-startup>1类加载中出现的顺序相同。如果“启动时加载”值不存在,每当<servlet><load-on-startup>4第一次命中该servlet时,将调用init()方法。
Servlet
<servlet><load-on-startup>
@WebServlet(loadOnStartup)
0
init()
1
2
当servlet容器完成上述所有初始化步骤时,将使用ServletContextEvent参数调用#0,该参数又包含所涉及的ServletContext。这将允许开发人员有机会以编程方式注册另一个Servlet、Filter或Listener。
ServletContextEvent
Filter
Listener
当servlet容器关闭时,它卸载所有Web应用程序,调用其所有初始化servlet和过滤器的destroy()方法,并且通过ServletContext注册的所有Servlet、Filter和Listener实例都被丢弃。最后#5将被调用,ServletContext本身将被丢弃。
destroy()
HttpServletRequest
HttpServletResponse
servlet容器附加到一个Web服务器,该服务器侦听特定端口号上的HTTP请求(端口8080通常在开发期间使用,端口80在生产中使用)。当客户端(例如使用Web浏览器的用户或以编程方式使用#0)发送HTTP请求时,servlet容器创建新的#1和#2对象,并将它们传递给链中任何定义的Filter,最终传递给Servlet实例。
在过滤器的情况下,调用doFilter()方法。当servlet容器的代码调用chain.doFilter(request, response)时,请求和响应继续到下一个过滤器,如果没有剩余的过滤器,则命中servlet。
doFilter()
chain.doFilter(request, response)
在servlet的情况下,调用service()方法。默认情况下,此方法根据request.getMethod()确定要调用doXxx()方法中的哪一个。如果servlet中没有确定的方法,则在响应中返回HTTP 405错误。
service()
request.getMethod()
doXxx()
请求对象提供了对HTTP请求的所有信息的访问,例如它的url、标题、查询字符串和主体。响应对象提供了以您想要的方式控制和发送HTTP响应的能力,例如,允许您设置标头和主体(通常使用从JSP文件生成的超文本标记语言内容)。当HTTP响应提交和完成时,请求和响应对象都被回收并可供重用。
HttpSession
当客户端第一次访问webapp和/或第一次通过request.getSession()获得#0时,servlet容器会创建一个新的HttpSession对象,生成一个长的唯一ID(可以通过session.getId()获得),并将其存储在服务器的内存中。servlet容器还在HTTP响应的Set-Cookie标头中设置一个#4,以JSESSIONID为名称,唯一会话ID为值。
request.getSession()
session.getId()
Set-Cookie
JSESSIONID
根据HTTP cookie规范(任何体面的Web浏览器和Web服务器都必须遵守的合同),只要cookie有效(即唯一ID必须指向未过期的会话,并且域和路径正确),客户端(Web浏览器)就需要在Cookie标头的后续请求中发送此cookie。使用浏览器的内置HTTP流量监视器,您可以验证cookie是否有效(在Chrome/Firefox 23+/IE9+中按F12,然后选中Net/Network选项卡)。servlet容器将检查每个传入HTTP请求的Cookie标头是否存在名称为JSESSIONID的cookie,并使用其值(会话ID)从服务器内存中获取关联的HttpSession。
Cookie
HttpSession保持活动状态,直到它空闲(即未在请求中使用)超过<session-timeout>中指定的超时值,这是web.xml中的设置。超时值默认为30分钟。因此,当客户端访问Web应用程序的时间超过指定的时间时,servlet容器会破坏会话。每个后续请求,即使指定了cookie,也将不再有权访问同一个会话;servlet容器将创建一个新会话。
<session-timeout>
在客户端,只要浏览器实例运行,会话cookie就会保持活动状态。因此,如果客户端关闭浏览器实例(所有选项卡/窗口),那么客户端的会话就会被破坏。在新的浏览器实例中,与会话关联的cookie将不存在,因此它将不再发送。这会导致创建一个全新的HttpSession,使用一个全新的会话cookie。
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.}}
当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(即HashTable),会话ID存储到htpservlet响应对象中,并将HttpSession对象的引用返回给servlet(doGet/doPost)。
步骤3:如果ID可用,则没有创建新的会话对象会话ID,则从请求对象中提取会话ID,通过使用会话ID作为键在会话集合中进行搜索。
搜索成功后,会话ID将存储到HttpServletResponse中,现有的会话对象引用将返回到UserDefeservlet的doGet()或doPost()。
1)当控制权从servlet代码转移到客户端时,不要忘记会话对象由servlet容器保存,即servlet引擎
2)多线程留给servlet开发人员实现,即处理客户端的多个请求,无需担心多线程代码
当应用程序启动时(它部署在servlet容器上)或第一次访问时(取决于启动时加载设置)创建servlet当servlet实例化时,servlet的init()方法被调用然后servlet(它的一个也是唯一的实例)处理所有请求(它的service()方法被多个线程调用)。这就是为什么不建议在其中进行任何同步,您应该避免servlet的实例变量当应用程序被取消部署(servlet容器停止)时,将调用销毁()方法。
简而言之:网络服务器在第一访问时向每位访客发出一个唯一标识符。访问者必须带回该ID,以便下次识别他。此标识符还允许服务器正确隔离一个会话拥有的对象与另一个会话拥有的对象。
如果启动时加载是虚假:
如果启动时加载是真正:
一旦他进入服务模式并处于最佳状态,相同 servlet将处理来自所有其他客户端的请求。
为什么每个客户都有一个实例不是一个好主意?想想这个:你会为每一个订单雇佣一个披萨人吗?这样做,你很快就会失业。
不过也有一个小风险,记住:这个单身汉把所有的订单信息都放在口袋里,所以如果你对servlet上的线程安全不小心,他可能会给某个客户下错误的订单。
Servlet规范JSR-315明确定义了服务中的Web容器行为(以及doGet、doPost、doput等)方法(2.3.3.1多线程问题,第9页):
servlet容器可以通过服务发送并发请求servlet的方法。为了处理请求,Servlet Developer必须为多个并发处理做好充分的准备服务方法中的线程。 虽然不推荐,但开发人员的另一种选择是实现SingleThreadModel接口,该接口需要容器来保证一次只有一个请求线程servlet容器可以通过以下方式满足此要求在servlet上序列化请求,或通过维护servlet池实例。如果servlet是Web应用程序的一部分,该应用程序已被标记为可分发,容器可以维护一个servlet池应用程序分布在每个JVM中的实例。 对于未实现SingleThreadModel接口的servlet,如果服务方法(或doGet或doPost等方法)分派到HttpServlet抽象类的服务方法)已使用同步关键字servlet容器定义不能使用实例池方法,但必须序列化请求。强烈建议开发者不要同步其中的服务方法(或分派给它的方法)对性能造成不利影响的情况
servlet容器可以通过服务发送并发请求servlet的方法。为了处理请求,Servlet Developer必须为多个并发处理做好充分的准备服务方法中的线程。
虽然不推荐,但开发人员的另一种选择是实现SingleThreadModel接口,该接口需要容器来保证一次只有一个请求线程servlet容器可以通过以下方式满足此要求在servlet上序列化请求,或通过维护servlet池实例。如果servlet是Web应用程序的一部分,该应用程序已被标记为可分发,容器可以维护一个servlet池应用程序分布在每个JVM中的实例。
对于未实现SingleThreadModel接口的servlet,如果服务方法(或doGet或doPost等方法)分派到HttpServlet抽象类的服务方法)已使用同步关键字servlet容器定义不能使用实例池方法,但必须序列化请求。强烈建议开发者不要同步其中的服务方法(或分派给它的方法)对性能造成不利影响的情况
不。 Servlet是不线程安全
这允许一次访问多个线程
如果你想让Servlet as Thread安全,你可以选择
Implement SingleThreadInterface(i)这是一个空白的接口,没有
Implement SingleThreadInterface(i)
方法
或者我们可以使用同步方法
我们可以通过使用同步来使整个服务方法同步
方法前关键字
考试e::
public Synchronized class service(ServletRequest request,ServletResponse response)throws ServletException,IOException
或者我们可以将代码的put块放在同步块中
Synchronized(Object) { ----Instructions----- }
我觉得同步块比整个方法更好
同步
从上面的解释中可以清楚地看出,通过实现单线程模型,servlet容器可以确保servlet的线程安全。容器实现可以通过两种方式做到这一点:
1)将请求序列化(排队)到单个实例-这类似于servlet不实现SingleThreadModel,而是同步service/doXXX方法;或者
2)创建一个实例池——这是一个更好的选择,也是servlet的启动/初始化工作/时间与托管servlet的环境的限制性参数(内存/CPU时间)之间的权衡。