您的位置:首页 > 编程语言 > Java开发

电商之梳理servlet知识---javaweb基础

2016-06-08 17:48 471 查看
servelt

是用java程序编写的服务器端程序,其主要功能是交互地浏览和修改数据(查询和修改)。狭义说的是接口,广义是指实现该接口的servlet类。

响应任何类型的请求,但是绝大数情况被用来 扩展基于HTTP协议的web服务器的性能。

起源:

首先说起java applet, 一种当做单独文件跟网页一起发送的小程序,通常在客户端运行,结果得到一些为用户进行运算,或者根据用户交互行为 定位图形等服务。

然而,提供用户输入访问数据库的程序,通常使用CGI(Common GateWay Interface)来完成的,并且执行速度较慢。

此时便产生了 java servlet。

实现过程

要了解他的过程,那么首先了解他的功能。

主要功能是:交互地浏览和修改数据,生成动态的web内容供用户使用。

1. 客户端发送请求至服务器端

2. 服务器端将请求信息发送至servlet类

3. servlet生成响应内容传至服务器,响应内容动态生成,取决于客户端的请求。

4. 服务器端将请求信息发送至servlet。

扩展:

1. servlet 导入了java servlet api相关的包

2. 对象字节码,可以被网络动态加载。

3. servlet运行与server中不需图形用户界面,applet运行在client环境中是需要图形用户界面。由此得来FacelessObject的别名。

4. 命名是sun公司的拿手好戏:当然是从applet里来的,一个script加上一个小应用程序=scriptlet 表示小脚本程序。

一个service加上一个小应用程序=servlet表示小服务程序。

生命周期

1. client—request—指定的servlet

2. load —-servlet—-内存中

3. 实例化—init()—初始化servlet

4. 调用类中的service() —-根据请求方式不同:选择:doGet()或者doPost() 还有其他方法不常用(doHead()、doPut()、doTrace()、doDelete()、doOptions())

5. 最后调用destroy()进行销毁。

分解其一生:

1. 加载过程

2. 实例化过程

3. 初始化过程

4. 销毁过程

加载和实例化 Servlet。这项操作一般是动态执行的。然而,Server 通常会提供一个管理的选项,用于在 Server 启动时强制装载和初始化特定的 Servlet。
Server 创建一个 Servlet的实例
第一个客户端的请求到达 Server
Server 调用 Servlet 的 init() 方法(可配置为 Server 创建 Servlet 实例时调用,在 web.xml 中 标签下配置 标签,配置的值为整型,值越小 Servlet 的启动优先级越高)
一个客户端的请求到达 Server
Server 创建一个请求对象,处理客户端请求
Server 创建一个响应对象,响应客户端请求
Server 激活 Servlet 的 service() 方法,传递请求和响应对象作为参数
service() 方法获得关于请求对象的信息,处理请求,访问其他资源,获得需要的信息
service() 方法使用响应对象的方法,将响应传回Server,最终到达客户端。service()方法可能激活其它方法以处理请求,如 doGet() 或 doPost() 或程序员自己开发的新的方法。
对于更多的客户端请求,Server 创建新的请求和响应对象,仍然激活此 Servlet 的 service() 方法,将这两个对象作为参数传递给它。如此重复以上的循环,但无需再次调用 init() 方法。一般 Servlet 只初始化一次(只有一个对象),当 Server 不再需要 Servlet 时(一般当 Server 关闭时),Server 调用 Servlet 的 destroy() 方法。
工作模式
编辑
客户端发送请求至服务器
服务器启动并调用 Servlet,Servlet 根据客户端请求生成响应内容并将其传给服务器
服务器将响应返回客户端
比较
编辑
与 Applet 的比较
相似之处:

它们不是独立的应用程序,没有 main() 方法。

它们不是由用户或程序员调用,而是由另外一个应用程序(容器)调用。

它们都有一个生存周期,包含 init() 和 destroy() 方法。

不同之处:

Applet具有很好的图形界面(AWT),与浏览器一起,在客户端运行。

Servlet 则没有图形界面,运行在服务器端。

与 CGI 比较
与传统的 CGI 和许多其他类似 CGI 的技术相比,Java Servlet 具有更高的效率,更容易使用,功能更强大,具有更好的可移植性,更节省投资。在未来的技术发展过程中,Servlet 有可能彻底取代 CGI。
在传统的 CGI中,每个请求都要启动一个新的进程,如果 CGI 程序本身的执行时间较短,启动进程所需要的开销很可能反而超过实际执行时间。而在 Servlet 中,每个请求由一个轻量级的 Java 线程处理(而不是重量级的操作系统进程)。
在传统 CGI 中,如果有 N 个并发的对同一 CGI程序的请求,则该CGI程序的代码在内存中重复装载了 N 次;而对于 Servlet,处理请求的是 N 个线程,只需要一份 Servlet 类代码。在性能优化方面,Servlet 也比 CGI 有着更多的选择。
各个用户请求被激活成单个程序中的一个线程,而无需创建单独的进程,这意味着服务器端处理请求的系统开销将明显降低。

方便

Servlet 提供了大量的实用工具例程,例如自动地解析和解码 HTML 表单数据、读取和设置 HTTP头、处理Cookie、跟踪会话状态等。

功能强大

在Servlet中,许多使用传统 CGI 程序很难完成的任务都可以轻松地完成。例如,Servlet 能够直接和 Web服务器交互,而普通的 CGI 程序不能。Servlet 还能够在各个程序之间共享数据,使得数据库连接池之类的功能很容易实现。

可移植性好

Servlet 用 Java 编写,Servlet API具有完善的标准。因此,为 IPlanet Enterprise Server 写的 Servlet 无需任何实质上的改动即可移植到 Apache、MicrosoftIIS 或者 WebStar。几乎所有的主流服务器都直接或通过插件支持 Servlet。

节省投资

不仅有许多廉价甚至免费的 Web 服务器可供个人或小规模网站使用,而且对于现有的服务器,如果它不支持 Servlet 的话,要加上这部分功能也往往是免费的(或只需要极少的投资)。
与 JSP 比较
JSP 和 Servlet 的区别到底在应用上有哪些体现,很多人搞不清楚。简单的说,SUN 首先发展出 Servlet,其功能比较强劲,体系设计也很先进,只是,它输出 HTML 语句还是采用了老的 CGI 方式,是一句一句输出,所以,编写和修改 HTML 非常不方便。
Java Server Pages(JSP)是一种实现普通静态HTML 和动态 HTML 混合编码的技术,JSP 并没有增加任何本质上不能用 Servlet 实现的功能。但是,在 JSP 中编写静态HTML 更加方便,不必再用 println语 句来输出每一行 HTML 代码。更重要的是,借助内容和外观的分离,页面制作中不同性质的任务可以方便地分开:比如,由页面设计者进行 HTML设计,同时留出供 Servlet 程序员插入动态内容的空间。
后来 SUN 推出了类似于 ASP 的镶嵌型的 JSP,把 JSP TAG 镶嵌到 HTML 语句中,这样,就大大简化和方便了网页的设计和修改。新型的网络语言如 ASP,PHP,JSP 都是镶嵌型的语言。 这是 JSP 和 Servlet 区别的运作原理层面。
从网络三层结构的角度看 JSP 和 Servlet 的区别,一个网络项目最少分三层:data layer(数据层),business layer(业务层),presentation layer(表现层)。当然也可以更复杂。Servlet 用来写 business layer 是很强大的,但是对于写 presentation layer 就很不方便。JSP 则主要是为了方便写 presentation layer 而设计的。当然也可以写 business layer。写惯了 ASP,PHP,CGI的朋友,经常会不自觉的把 presentation layer 和 business layer 混在一起。
根据 SUN 自己的推荐,JSP中应该仅仅存放与 presentation layer 有关的东西,也就是说,只放输出 HTML 网页的部分。而所有的数据计算,数据分析,数据库联结处理,统统是属于 business layer,应该放在 Java BEANS 中。通过 JSP 调用 Java BEANS,实现两层的整合。
实际上,微软前不久推出的 DNA 技术,简单说,就是 ASP+COM/DCOM 技术。与J SP+BEANS 完全类似,所有的 presentation layer 由 ASP 完成,所有的 business layer 由 COM/DCOM 完成。通过调用,实现整合。
为什么要采用这些组件技术呢?因为单纯的 ASP/JSP 语言是非常低效率执行的,如果出现大量用户点击,纯 SCRIPT 语言很快就到达了他的功能上限,而组件技术就能大幅度提高功能上限,加快执行速度。
另外一方面,纯 SCRIPT 语言将 presentation layer 和 business layer 混在一起,造成修改不方便,并且代码不能重复利用。如果想修改一个地方,经常会牵涉到十几页 code,采用组件技术就只改组件就可以了。
综上所述,Servlet 是一个早期的不完善的产品,写 business layer 很好,写 presentation layer 就很臭,并且两层混杂。
所以,推出JSP+BEAN,用 JSP 写 presentation layer,用 BEAN 写 business layer。SUN 自己的意思也是将来用 JSP 替代 Servlet。这是技术更新方面 JSP 和 Servlet 的区别。
可是,这不是说,学了 Servlet 没用,实际上,你还是应该从 Servlet 入门,再上 JSP,再上 JSP+BEAN。
强调的是:学了JSP,不会用 Java BEAN 并进行整合,等于没学。大家多花点力气在 JSP+BEAN 上。
我们可以看到,当 ASP+COM 和 JSP+BEAN 都采用组件技术后,所有的组件都是先进行编译,并驻留内存,然后快速执行。所以,大家经常吹的 Servlet/JSP 先编译驻内存后执行的速度优势就没有了。
反之,ASP+COM+IIS+NT 紧密整合,应该会有较大的速度优势呈现。而且,ASP+COM+IIS+NT 开发效率非常高,虽然bug 很多。
那么,为什么还用 JSP+BEAN?因为 Java 实在前途远大。微软分拆后,操作系统将群雄并起,应用软件的开发商必定要找一个通用开发语言进行开发,Java 一统天下的时机就到了。如果微软分拆顺利,从中分出的应用软件公司将成为 Java 的新领导者。目前的 Java 大头 SUN 和 IBM 都死气沉沉,令人失望。希望新公司能注入新活力。不过,新公司很有可能和旧 SUN 展开 Java 标准大战,双方各自制定标准,影响 Java 跨平台。
简单分析了一下 JSP 和 Servlet 的区别和 Java Web 开发方面的发展。随着机器速度越来越快,Java 的速度劣势很快就可以被克服。
新增功能
编辑
Servlet 2.2
:引入了 self-contained Web applications 的概念。
servlet 2.3
2000年10月份出来

Servlet API 2.3中最重大的改变是增加了 filters

Servlet 2.3 增加了 filters 和 filter chains 的功能。引入了 context 和 session listeners 的概念,当 context 或 session 被初始化或者被将要被释放的时候,和当向 context 或 session 中绑定属性或解除绑定的时候,可以对类进行监测。

servlet 2.4

2003年11月份出来

Servlet 2.4 加入了几个引起关注的特性,没有特别突出的新内容,而是花费了更多的功夫在推敲和阐明以前存在的一些特性上,对一些不严谨的地方进行了校验。

Servlet 2.4 增加了新的最低需求,新的监测 request 的方法,新的处理 response 的方法,新的国际化支持,RequestDispatcher 的几个处理,新的 request listener 类,session 的描述,和一个新的基于 Schema 的并拥有 J2EE 元素的发布描述符。这份文档规范全面而严格的进行了修订,除去了一些可能会影响到跨平台发布的模糊不清的因素。总而言之,这份规范增加了四个新类,七个新方法,一个新常量,不再推荐使用一个类。

注意:改为 Schema 后主要加强了两项功能:

(1) 元素不依照顺序设定;

(2) 更强大的验证机制。

主要体现在:

a.检查元素的值是否为合法的值

b.检查元素的值是否为合法的文字字符或者数字字符

c.检查 Servlet, Filter, EJB-ref 等等元素的名称是否唯一

2.新增 Filter 四种设定:REQUEST、FORWARD、INCLUDE 和 ERROR。

3.新增 Request Listener、Event和Request Attribute Listener、Event。

4.取消 SingleThreadModel 接口。当 Servlet 实现 SingleThreadModel 接口时,它能确保同时间内,只能有一个 thread 执行此 Servlet。

5.可以为Servlet。

6.ServletRequest接口新增一些方法。

public String getLocalName();

public String getLocalAddr();

public int getLocalPort();

public int getRemotePort()

Servlet 2.5

2005 年 9 月发布 Servlet 2.5

Servlet 2.5 一些变化的介绍:

1) 基于最新的 J2SE 5.0 开发的。

2) 支持 annotations 。

3) web.xml 中的几处配置更加方便。

4) 去除了少数的限制。

5) 优化了一些实例

Servlet 的各个版本对监听器的变化有:

(1) Servlet 2.2 和 jsp1.1

新增Listener:HttpSessionBindingListener

新增Event: HttpSessionBindingEvent

(2) Servlet 2.3 和 jsp1.2

新增Listener:ServletContextListener,ServletContextAttributeListener

,HttpSessionListener,HttpSessionActivationListener,HttpSessionAttributeListener

新增Event: ServletContextEvent,ServletContextAttributeEvent,HttpSessionEvent

(3) Servlet 2.4 和 jsp2.0

新增Listener:ServletRequestListener,ServletRequestAttribureListener

新增Event: ServletRequestEvent,ServletRequestAttributeEvent

Servlet 3.0

Servlet 3.0 作为 Java EE 6 规范体系中一员[1] ,随着 Java EE 6 规范一起发布。该版本在前一版本(Servlet 2.5)的基础上提供了若干新特性用于简化 Web 应用的开发和部署。其中有几项特性的引入让开发者感到非常兴奋,同时也获得了 Java 社区的一片赞誉之声:[2]

异步处理支持:有了该特性,Servlet 线程不再需要一直阻塞,直到业务处理完毕才能再输出响应,最后才结束该 Servlet 线程。在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。[2]

新增的注解支持:该版本新增了若干注解,用于简化 Servlet、过滤器(Filter)和监听器(Listener)的声明,这使得 web.xml 部署描述文件从该版本开始不再是必选的了。[2]

可插性支持:熟悉 Struts2 的开发者一定会对其通过插件的方式与包括 Spring 在内的各种常用框架的整合特性记忆犹新。将相应的插件封装成 JAR 包并放在类路径下,Struts2 运行时便能自动加载这些插件。现在 Servlet 3.0 提供了类似的特性,开发者可以通过插件的方式很方便的扩充已有 Web 应用的功能,而不需要修改原有的应用。[2]

Servlet 4.0草案

从3.1到4.0将是对Servlet 协议的一次大改动,而改动的关键之处在于对HTTP/2的支持。HTTP2将是是继上世纪末HTTP1.1协议规范化以来首个HTTP协议新版本,相对于HTTP1.1,HTTP2将带来许多的增强。在草案提议中,Shing Wai列举出了一些HTTP2的新特性,而这些特性也正是他希望在Servlet 4.0 API中实现并暴露给用户的新功能,这些新特性如下:[3]

1.请求/响应复用(Request/Response multiplexing)

  2.流的优先级(Stream Prioritization)

  3.服务器推送(Server Push)

  4.HTTP1.1升级(Upgrade from HTTP 1.1)[3]

规范

编辑

1.简化开发

2.便于部署

3.支持 Web2.0 原则

为了简化开发流程,Servlet 3.0 引入了注解(annotation),这使得 web 部署描述符 web.xml 不再是必须的选择。

Pluggability可插入性

当使用任何第三方的框架,如 Struts,JSF 或 Spring,我们都需要在 web.xml 中添加对应的 Servlet 的入口。这使得 web 描述符笨重而难以维护。Servlet3.0 的新的可插入特性使得 web 应用程序模块化而易于维护。通过 web fragment 实现的可插入性减轻了开发人员的负担,不需要再在 web.xml 中配置很多的 Servlet 入口。

Asynchronous Processing 异步处理

另外一个显著的改变就是 Servlet 3.0 支持异步处理,这对 AJAX 应用程序非常有用。当一个 Servlet 创建一个线程来处理某些请求的时候,如查询数据库或消息连接,这个线程要等待直到获得所需要的资源才能够执行其他的操作。异步处理通过运行线程执行其他的操作来避免了这种阻塞。

Apart from the features mentioned here, several other enhancements have been made to the existing API. The sections towards the end of the article will explore these features one by one in detail.

除了这些新特性之外, Servlet 3.0对已有的 API 也做了一些改进,在本文的最后我们会做介绍。

Annotations in Servlet Servlet中使用注解

Servlet 3.0 的一个主要的改变就是支持注解。使用注解来定义 Servlet 和 filter 使得我们不用在 web.xml 中定义相应的入口。

@WebServlet

@WebServlet 用来定义 web 应用程序中的一个 Servlet。这个注解可以应用于继承了 HttpServlet。这个注解有多个属性,例如 name,urlPattern, initParams,我们可以使用者的属性来定义 Servlet 的行为。urlPattern 属性是必须指定的。

编程接口

编辑

HTTPServlet 使用一个 HTML 表单来发送和接收数据。要创建一个 HTTPServlet,请扩展 HttpServlet 类, 该类是用专门的方法来处理 HTML 表单的 GenericServlet 的一个子类。 HTML 表单是由 和 标记定义的。表单中典型地包含输入字段(如文本输入字段、复选框、单选按钮和选择列表)和用于提交数据的按钮。当提交信息时,它们还指定服务器应执行哪一个Servlet(或其它的程序)。 HttpServlet 类包含 init()、destroy()、service() 等方法。其中 init() 和 destroy() 方法是继承的。

(1) init() 方法

在 Servlet 的生命期中,仅执行一次 init() 方法。它是在服务器装入 Servlet 时执行的。 可以配置服务器,以在启动服务器或客户机首次访问 Servlet 时装入 Servlet。 无论有多少客户机访问 Servlet,都不会重复执行 init() 。

缺省的 init() 方法通常是符合要求的,但也可以用定制 init() 方法来覆盖它,典型的是管理服务器端资源。 例如,可能编写一个定制 init() 来只用于一次装入 GIF 图像,改进 Servlet 返回 GIF 图像和含有多个客户机请求的性能。另一个示例是初始化数据库连接。缺省的 init() 方法设置了 Servlet 的初始化参数,并用它的 ServletConfig 对象参数来启动配置, 因此所有覆盖 init() 方法的 Servlet 应调用 super.init() 以确保仍然执行这些任务。在调用 service() 方法之前,应确保已完成了 init() 方法。

(2) service() 方法

service() 方法是 Servlet 的核心。每当一个客户请求一个HttpServlet 对象,该对象的service() 方法就要被调用,而且传递给这个方法一个”请求”(ServletRequest)对象和一个”响应”(ServletResponse)对象作为参数。 在 HttpServlet 中已存在 service() 方法。缺省的服务功能是调用与 HTTP 请求的方法相应的 do 功能。例如, 如果 HTTP 请求方法为 GET,则缺省情况下就调用 doGet() 。Servlet 应该为 Servlet 支持的 HTTP 方法覆盖 do 功能。因为 HttpServlet.service() 方法会检查请求方法是否调用了适当的处理方法,不必要覆盖 service() 方法。只需覆盖相应的 do 方法就可以了。

Servlet 的响应可以是下列几种类型:

一个输出流,浏览器根据它的内容类型(如 text/html)进行解释。

一个 HTTP 错误响应,重定向到另一个 URL、servlet、JSP。

(3) doGet() 方法

当一个客户通过 HTML 表单发出一个 HTTP GET 请求或直接请求一个 URL 时,doGet() 方法被调用。与 GET 请求相关的参数添加到 URL 的后面,并与这个请求一起发送。当不会修改服务器端的数据时,应该使用 doGet() 方法。

(4) doPost() 方法

当一个客户通过 HTML 表单发出一个 HTTP POST 请求时,doPost() 方法被调用。与 POST 请求相关的参数作为一个单独的 HTTP 请求从浏览器发送到服务器。当需要修改服务器端的数据时,应该使用 doPost() 方法。

(5) destroy() 方法

destroy() 方法仅执行一次,即在服务器停止且卸装 Servlet 时执行该方法。典型的,将 Servlet 作为服务器进程的一部分来关闭。缺省的 destroy() 方法通常是符合要求的,但也可以覆盖它,典型的是管理服务器端资源。例如,如果 Servlet 在运行时会累计统计数据,则可以编写一个 destroy() 方法,该方法用于在未装入 Servlet 时将统计数字保存在文件中。另一个示例是关闭数据库连接。

当服务器卸装 Servlet 时,将在所有 service() 方法调用完成后,或在指定的时间间隔过后调用 destroy() 方法。一个 Servlet 在运行 service() 方法时可能会产生其它的线程,因此请确认在调用 destroy() 方法时,这些线程已终止或完成。

(6) getServletConfig() 方法

getServletConfig() 方法返回一个 ServletConfig 对象,该对象用来返回初始化参数和 ServletContext。ServletContext 接口提供有关 servlet 的环境信息。

(7) getServletInfo() 方法

getServletInfo() 方法是一个可选的方法,它提供有关 servlet 的信息,如作者、版本、版权。

当服务器调用 sevlet 的 service()、doGet() 和 doPost() 这三个方法时,均需要 “请求”和“响应”对象作为参数。“请求”对象提供有关请求的信息,而“响应”对象提供了一个将响应信息返回给浏览器的一个通信途径。

javax.servlet 软件包中的相关类为 ServletResponse 和 ServletRequest,而 javax.servlet.http 软件包中的相关类为 HttpServletRequest 和 HttpServletResponse。Servlet 通过这些对象与服务器通信并最终与客户端通信。Servlet 能通过调用”请求”对象的方法获知客户端环境,服务器环境的信息和所有由客户机提供的信息。Servlet 可以调用“响应”对象的方法发送响应,该响应是准备发回客户端的。

常见容器

编辑

Tomcat, Jetty, resin, Oracle Application server, WebLogic Server, Glassfish, Websphere, JBoss 等等。(提供了 Servlet 功能的服务器,叫做 Servlet 容器。对 web 程序来说,Servlet 容器的作用就相当于桌面程序里操作系统的作用,都是提供一些编程基础设施)

建议

编辑

在 Web 应用程序中,一个 Servlet 在一个时刻可能被多个用户同时访问。这时 Web 容器将为每个用户创建一个线程来执行 Servlet。如果 Servlet 不涉及共享资源的问题,不必关心多线程问题。但如果 Servlet 需要共享资源,需要保证 Servlet 是线程安全的。

下面是编写线程安全的 Servlet 的一些建议:

(1)用方法的局部变量保存请求中的专有数据。对方法中定义的局部变量,进入方法的每个线程都有自己的一份方法变量拷贝。任何线程都不会修改其他线程的局部变量。如果要在不同的请求之间共享数据,应该使用会话来共享这类数据。

(2)只用 Servlet的成员变量来存放那些不会改变的数据。有些数据在 Servlet 生命周期中不发生任何变化,通常是在初始时确定的,这些数据可以使用成员变量保存,如数据库连接名称、其他资源的路径等。

(3)对可能被请求修改的成员变量同步。有时数据成员变量或者环境属性可能被请求修改。当访问这些数据时应该对它们同步,以避免多个线程同时修改这些数据。

(4)如果 Servlet 访问外部资源,那么需要同步访问这些资源。例如,假设 Servlet 要从文件中读写数据。当一个线程读写一个文件时,其他线程也可能正在读写这个文件。文件访问本身不是线程安全的,所以必须编写同步访问这些资源的代码。在编写线程安全的 Servlet 时,下面两种方法是不应该使用的:

(1)在 Servlet API 中提供了一个 SingleThreadModel 接口,实现这个接口的 Servlet 在被多个客户请求时一个时刻只有一个线程运行。这个接口已被标记不推荐使用。

(2)对 doGet() 或doPost() 方法同步。如果必须在 Servlet 中使用同步代码,应尽量在最小的代码块范围上进行同步。同步代码越小,Servlet 执行得才越好。[4]

servlet在spring中加载的顺序

2013-05-22 19:27:50| 分类: java |举报|字号 订阅

下载LOFTER我的照片书 |

spring中,servlet执行init方法的时机。

web.xml中常用的servlet定义:

assembler

com.branchitech.app.startup.AppStartupWrappedServlet

targetProxyClass

org.springframework.web.servlet.DispatcherServlet

2

assembler

*.do

其中2标签定义了servlet载入的顺序:

1)load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。

2)它的值必须是一个整数,表示servlet应该被载入的顺序

2)当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet;

3)当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。

4)正数的值越小,该servlet的优先级越高,应用启动时就越先加载。

5)当值相同时,容器就会自己选择顺序来加载

在运行时,Servlet容器的类加载器先加载classes目录下的类,再加载lib目录下的JAR文件中的类。因此,如果两个目录下存在同名的类,classes目录下的类具有优先权。

我们注意到Tomcat的安装目录下也有一个lib目录,这个与Web应用中的lib目录的区别在于:

Tomcat的lib子目录:存放的JAR文件不仅能被Tomcat访问,还能被所有在Tomcat中发布的JavaWeb应用访问。

JavaWeb应用的lib子目录:存放的JAR文件只能被当前JavaWeb应用访问。

假如Tomcat类加载器要加载一个MyClass的类,它会按照以下先后顺序到各个目录中去查找MyClass的class文件,直到找到为止,如果所有目录中都不存在MyClass.class的文件,则会抛出异常:

1、在JavaWeb应用的WEB-INF/classes中查找MyClass.class文件。

2、在JavaWeb应用的 WEB-INF/lib目录下的JAR文件中查找MyClass.class文件。

3、在Tomcat的lib子目录下直接查找MyClass.class文件。

4、在Tomcat的lib子目录下JAR的文件中查找MyClass.class文件。

1、装载并实例化Servlet(在整个生命周期中Servlet实例只有一个)

分为两种装载方式:

a) 延迟装载(默认方式)

当客户端发起一个请求第一次去访问Servlet时,容器会将Servlet装载进虚拟机并实例化,第二次以后去访问同一个Servlet时容器就不会再去装载并实例化。


b) 预先装载

当Web Server启动,容器在装载Web应用的时候会将Servlet装载进虚拟机并实例化。


这种方式必须在web.xml中描述:

<servlet>

...

<load-on-startup>

number

</load-on-startup>

</servlet>

number<0: 采用延迟装载

number>=0: 采用预先装载

number越小越先被装载,number越大越晚被装载

number=0最晚被装载


一,servlet容器如何同时处理多个请求。

Servlet采用多线程来处理多个请求同时访问,Servelet容器维护了一个线程池来服务请求。

线程池实际上是等待执行代码的一组线程叫做工作者线程(Worker Thread),Servlet容器使用一个调度线程来管理工作者线程(Dispatcher Thread)。

当容器收到一个访问Servlet的请求,调度者线程从线程池中选出一个工作者线程,将请求传递给该线程,然后由该线程来执行Servlet的service方法。

当这个线程正在执行的时候,容器收到另外一个请求,调度者线程将从池中选出另外一个工作者线程来服务新的请求,容器并不关系这个请求是否访问的是同一个Servlet还是另外一个Servlet。

当容器同时收到对同一Servlet的多个请求,那这个Servlet的service方法将在多线程中并发的执行。

二,Servlet容器默认采用单实例多线程的方式来处理请求,这样减少产生Servlet实例的开销,提升了对请求的响应时间。对于Tomcat可以在server.xml中通过元素设置线程池中线程的数目。

就实现来说:

调度者线程类所担负的责任如其名字,该类的责任是调度线程,只需要利用自己的属性完成自己的责任。所以该类是承担了责任的,并且该类的责任又集中到唯一的单体对象中。

而其他对象又依赖于该特定对象所承担的责任,我们就需要得到该特定对象。那该类就是一个单例模式的实现了。

三,如何开发线程安全的Servlet

1,变量的线程安全:这里的变量指字段和共享数据(如表单参数值)。

a,将 参数变量 本地化。多线程并不共享局部变量.所以我们要尽可能的在servlet中使用局部变量。

例如:String user = “”;

user = request.getParameter(“user”);

b,使用同步块Synchronized,防止可能异步调用的代码块。这意味着线程需要排队处理。

在使用同板块的时候要尽可能的缩小同步代码的范围,不要直接在sevice方法和响应方法上使用同步,这样会严重影响性能。

2,属性的线程安全:ServletContext,HttpSession,ServletRequest对象中属性

ServletContext:(线程是不安全的)

ServletContext是可以多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clone()。

所以在Servlet上下文中尽可能少量保存会被修改(写)的数据,可以采取其他方式在多个Servlet中共享,比方我们可以使用单例模式来处理共享数据。

HttpSession:(线程是不安全的)

HttpSession对象在用户会话期间存在,只能在处理属于同一个Session的请求的线程中被访问,因此Session对象的属性访问理论上是线程安全的。

当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,可能造成同时多线程读写属性。

这时我们需要对属性的读写进行同步处理:使用同步块Synchronized和使用读/写器来解决。

ServletRequest:(线程是安全的)

对于每一个请求,由一个工作线程来执行,都会创建有一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。

注意:ServletRequest对象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存请求对象的引用。

3,使用同步的集合类:

使用Vector代替ArrayList,使用Hashtable代替HashMap。

4,不要在Servlet中创建自己的线程来完成某个功能。

Servlet本身就是多线程的,在Servlet中再创建线程,将导致执行情况复杂化,出现多线程安全问题。

5,在多个servlet中对外部对象(比方文件)进行修改操作一定要加锁,做到互斥的访问。

四,SingleThreadModel接口

javax.servlet.SingleThreadModel接口是一个标识接口,如果一个Servlet实现了这个接口,那Servlet容器将保证在一个时刻仅有一个线程可以在给定的servlet实例的service方法中执行。将其他所有请求进行排队。

服务器可以使用多个实例来处理请求,代替单个实例的请求排队带来的效益问题。服务器创建一个Servlet类的多个Servlet实例组成的实例池,对于每个请求分配Servlet实例进行响应处理,之后放回到实例池中等待下此请求。这样就造成并发访问的问题。

此时,局部变量(字段)也是安全的,但对于全局变量和共享数据是不安全的,需要进行同步处理。而对于这样多实例的情况SingleThreadModel接口并不能解决并发访问问题。

SingleThreadModel接口在servlet规范中已经被废弃了。

怎样理解Servlet的单实例多线程 2013-05-14 15:34:39

分类: Java

首先明确:Servlet是单实例的,即对于同一种业务请求只有一个是实例。不同的业务请求可以通过分发来产生多个实例。

其次:单实例的原因我想是因为单实例足可以处理某一个请求,就像ibatis的Querydao、UpdateDao一样都是单实例的。

再次:为什么单实例足可以处理某一个请求,因为Servlet是单实例多线程的。

http://hiyachen.cublog.cn chf@tsinghua.org.cn

先看一段代码:

package hiya.test;

public class Servlet {

private static Servlet instance=new Servlet();

private Servlet(){

}

public static Servlet getInstance(){

return instance;

}

public void services(){

System.out.println(“do something”);

}

static class Client extends Thread{

private Servlet servlet;

public Client(Servlet servlet){

this.servlet=servlet;

}

public void run(){

servlet.services();

//System.out.println(“do something”);

}

}

public static void main(){

Servlet servlet=Servlet.getInstance();

for(int i=0;i<10;i++){

Client client=new Client(servlet);

client.start();

}

}

}

这是单实例多线程的实现代码。(真想把jdk源码贴出来。)

servlet单实例多线程处理原理:

servlet中的init方法只有在启动(例如web容器启动,要看loadOnStartup的设置)的时候调用,也就是只初始化一次,这就是单实例。

servlet在处理请求的时候 调用的是service方法,这个方法可以处理多个客户端的请求。

具体访问时:

JSP 在web容器中”翻译成servlet”由容器执行,web 容器本身就是提供的多线程,A,B,C 3个访问,建立3个独立的线程组,然后运行一个servlet。依次执行。

这就解决了多用户同一实例实行的困惑。

要注意几点:

1:servlet首先不是现成线程的。

2:Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。

Servlet容器会自动使用线程池等技术来支持系统的运行

3:设定jsp:<%@ page isThreadSafe=”false”%>来实现单线程。

当你需要保证数据一致性的时候,必须自己处理线程安全问题时可以考虑单线程。

Servlet多线程同步问题(important)

最主要的原因之一是:Servlet是单例模式,一个Servlet给所有的用户提供服务,有可能造成资源混乱的模式

Servlet的多线程同步问题:Servlet本身是单实例的,这样当有多个用户同时访问某个Servlet时,会访问该唯一的Servlet实例中的成员变量,如果对成员变量进行写入操作,那就会导致Servlet的多线程问题,即数据不一致。

解决同步问题的方案:

1、去除实例变量,使用局部变量(最好的)

2、使用synchronized{}

1、Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。

2、由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的同步问题。

3、如果在编写Servlet/JSP程序时不注意到多线程同步的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题,对于这类随机性的问题调试难度也很大。

例题:

public class ThreadServlet extends HttpServlet{

private String username;

因为是成员变量,又是单例的,所以这个username共享资源

@Override

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

username = req.getParameter(“username”);

try{

Thread.sleep(4000);

}catch(Exception e){

e.printStackTrace();

}

req.setAttribute(“username”, this.username);

req.getRequestDispatcher(“counter.jsp”).forward(req, resp);

}

}

填表单的时候一个填的是baliang,一个是banjin,结果却都为banjin,这就是多线程资源共享问题!~

JSP/Servlet的多线程原理:

1.servelet就是一个CGI,但比传统的CGI要快得过

传统CGI是多进程的,servlet是多线程的

以多线程方式执行可大大降低对系统的资源需求,提高 系统的并发量及响应时间.

JSP/Servlet容器默认是采用单实例多线程(这是造成线程安全的主因)方式处理多个请求的:

当客户端第一次请求某一个JSP文件时(有的servlet是随容器启动就startup):

服务端把该JSP编译成一个CLASS文件

并创建一个该类的实例

然后创建一个线程处理CLIENT端的请求。

多请求,多线程:

如果有多个客户端同时请求该JSP文件,则服务端会创建多个线程。每个客户端请求对应一个线程。

servlet 的线程安全

servlet里的 实例变量

servlet里的实例变量,是被所有线程共享的,所以不是线程安全的.

servlet方法里的局部变量

因为每个线程都有它自己的堆栈空间,方法内局部变量存储在这个线程堆栈空间内,

且参数传入方法是按传值volue copy的方式

所以是线程安全的

Application对象

在container运行期间,被整个系统内所有用户共同使用,所以不是线程安全 的

ServletContext对象

ServletContext是可以多线程同时读/写属性的,线程是不安全的。

struts2 的ServletContext采用的是TreadLocal模式,是线程安全的

HttpServletRequest对象和HttpServletResponse对象

每一个请求,由一个工作线程来执行,都会创建有一对新的ServletRequest对象和ServletResponse,然后传入service()方法内

所以每个ServletRequest对象对应每个线程,而不是多线程共享,是线程安全的。所以不用担心request参数和属性的线程安全性

HttpSession

Session对象在用户session期间存在,只能在属于同一个SessionID的请求的线程中被访问,因此Session对象的理论上是线程安全的。

(当用户打开多个同属于一个进程的浏览器窗口(常见的弹出窗口),在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,这时就有可能的出现线程安全问题)

servlet 尽量用方法内变量,就一定线程安全么? 局部变量的数据也来自request对象或session对象啊,它们线程安全么?

servletRequest 线程是安全的

因为:每个 request 都会创建一个新线程,每个新线程,容器又都会创建一对servletRequest和servletResponse对象(这是servlet基本原理)

所以servletRequest对象和servletResponse对象只在一个线程内被创建,存在,被访问

常见的线程安全的解决办法:

1.使用方法内局部变量

是因为各线程有自己堆栈空间,存储局部变量

方法参数传入,多采用传值(volue copy)传入方法内

2.对操作共享资源的语句,方法,对象, 使用同步

比如写入磁盘文件,采用同步锁,但建议尽量用同步代码块,不要用同步方法

3.使用同步的集合类

使用Vector代替ArrayList

使用Hashtable代替HashMap。

4.不要在 Servlet中再创建自己的线程来完成某个功能。

Servlet本身就是多线程的,在Servlet中再创建线程,将导致执行情况复杂化

Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。 这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。

实例变量不正确的使用是造成Servlet线程不安全的主要原因。下面针对该问题给出了三种解决方案并对方案的选取给出了一些参考性的建议。

  1、实现 SingleThreadModel 接口

  该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。这种方法只要将前面的Concurrent Test类的类头定义更改为:

Public class Concurrent Test extends HttpServlet implements SingleThreadModel {

…………

}

  2、同步对共享数据的操作

  使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,在本论文中的Servlet可以通过同步块操作来保证线程的安全。同步后的代码如下:

…………

Public class Concurrent Test extends HttpServlet { …………

Username = request.getParameter (“username”);

Synchronized (this){

Output = response.getWriter ();

Try {

Thread. Sleep (5000);

} Catch (Interrupted Exception e){}

output.println(“用户名:”+Username+”

“);

}

}

}

  3、避免使用实例变量

  本实例中的线程安全问题是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。

  修正上面的Servlet代码,将实例变量改为局部变量实现同样的功能,代码如下:

……

Public class Concurrent Test extends HttpServlet {public void service (HttpServletRequest request, HttpServletResponse

Response) throws ServletException, IOException {

Print Writer output;

String username;

Response.setContentType (“text/html; charset=gb2312”);

……

}

}

  对上面的三种方法进行测试,可以表明用它们都能设计出线程安全的Servlet程序。但是,如果一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销。SingleThreadModel在Servlet2.4中已不再提倡使用;同样如果在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大下降。这是因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。另外为保证主存内容和线程的工作内存中的数据的一致性,要频繁地刷新缓存,这也会大大地影响系统的性能。所以在实际的开发中也应避免或最小化 Servlet 中的同步代码;在Serlet中避免使用实例变量是保证Servlet线程安全的最佳选择。从Java 内存模型也可以知道,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。

补充:

servlet存在的多线程问题

实例变量: 实例变量是在堆中分配的,并被属于该实例的所有线程共享,所以不是线程安全的.

JSP系统提供的8个类变量:

JSP中用到的OUT,REQUEST,RESPONSE,SESSION,CONFIG,PAGE,PAGECONXT是线程安全的,APPLICATION在整个系统内被使用,所以不是线程安全的.

局部变量: 局部变量在堆栈中分配,因为每个线程都有它自己的堆栈空间,所以是线程安全的.

静态类: 静态类不用被实例化,就可直接使用,也不是线程安全的.

外部资源: 在程序中可能会有多个线程或进程同时操作同一个资源(如:多个线程或进程同时对一个文件进行写操作).

此时也要注意同步问题. 使它以单线程方式执行,这时,仍然只有一个实例,所有客户端的请求以串行方式执行。这样会降低系统的性能

对于存在线程不安全的类,如何避免出现线程安全问题:

1、采用synchronized同步。缺点就是存在堵塞问题。

2、使用ThreadLocal(实际上就是一个HashMap),这样不同的线程维护自己的对象,线程之间相互不干扰。

ThreadLocal的设计

首先看看ThreadLocal的接口:

Object get() ; // 返回当前线程的线程局部变量副本 protected Object

initialValue(); // 返回该线程局部变量的当前线程的初始值

void set(Object value); // 设置当前线程的线程局部变量副本的值

  ThreadLocal有3个方法,其中值得注意的是initialValue(),该方法是一个protected

的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始

值,这个方法是一个延迟调用方法,在一个线程第1次调用get()或者set(Object)时才执行

,并且仅执行1次。ThreadLocal中的确实实现直接返回一个null:

protected Object initialValue() { return null; }

  ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,

在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。比如下面的示例实现:

public class ThreadLocal

{

 private Map values = Collections.synchronizedMap(new HashMap());

 public Object get()

 {

  Thread curThread = Thread.currentThread();

  Object o = values.get(curThread);

  if (o == null && !values.containsKey(curThread))

  {

   o = initialValue();

   values.put(curThread, o);

  }

  return o;

 }

 public void set(Object newValue)

 {

  values.put(Thread.currentThread(), newValue);

 }

 public Object initialValue()

 {

  return null;

 }

}

  当然,这并不是一个工业强度的实现,但JDK中的ThreadLocal的实现总体思路也类似于此。

ThreadLocal的使用

  如果希望线程局部变量初始化其它值,那么需要自己实现ThreadLocal的子类并重写该

方法,通常使用一个内部匿名类对ThreadLocal进行子类化,比如下面的例子,SerialNum类

为每一个类分配一个序号:

public class SerialNum

{

 // The next serial number to be assigned

 private static int nextSerialNum = 0;

 private static ThreadLocal serialNum = new ThreadLocal()

 {

  protected synchronized Object initialValue()

  {

   return new Integer(nextSerialNum++);

  }

 };

 public static int get()

 {

  return ((Integer) (serialNum.get())).intValue();

 }

}

  SerialNum类的使用将非常地简单,因为get()方法是static的,所以在需要获取当前线

程的序号时,简单地调用:

int serial = SerialNum.get(); 即可。

  在线程是活动的并且ThreadLocal对象是可访问的时,该线程就持有一个到该线程局部

变量副本的隐含引用,当该线程运行结束后,该线程拥有的所以线程局部变量的副本都将失

效,并等待垃圾收集器收集。

ThreadLocal与其它同步机制的比较

  ThreadLocal和其它同步机制相比有什么优势呢?ThreadLocal和其它所有的同步机制都

是为了解决多线程中的对同一变量的访问冲突,在普通的同步机制中,是通过对象加锁来实

现多个线程对同一变量的安全访问的。这时该变量是多个线程共享的,使用这种同步机制需

要很细致地分析在什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放该

对象的锁等等很多。所有这些都是因为多个线程共享了资源造成的。ThreadLocal就从另一

个角度来解决多线程的并发访问,ThreadLocal会为每一个线程维护一个和该线程绑定的变

量的副本,从而隔离了多个线程的数据,每一个线程都拥有自己的变量副本,从而也就没有

必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时

,可以把不安全的整个变量封装进ThreadLocal,或者把该对象的特定于线程的状态封装进

ThreadLocal。

  由于ThreadLocal中可以持有任何类型的对象,所以使用ThreadLocal get当前线程的值

是需要进行强制类型转换。但随着新的Java版本(1.5)将模版的引入,新的支持模版参数

的ThreadLocal类将从中受益。也可以减少强制类型转换,并将一些错误检查提前到了编

译期,将一定程度地简化ThreadLocal的使用。

总结

当然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同

步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而

ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量)

,这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,

则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极

大地简化你的程序,使程序更加易读、简洁。

ThreadLocal常见用途:

存放当前session用户

存放一些context变量,比如webwork的ActionContext

存放session,比如spring hibernate orm的session

例子:用 ThreadLocal 实现每线程 Singleton

线程局部变量常被用来描绘有状态“单子”(Singleton) 或线程安全的共享对象,或者是通过把不安全的整个变量封装进 ThreadLocal,或者是通过把对象的特定于线程的状态封装进 ThreadLocal。例如,在与数据库有紧密联系的应用程序中,程序的很多方法可能都需要访问数据库。在系统的每个方法中都包含一个 Connection 作为参数是不方便的 — 用“单子”来访问连接可能是一个虽然更粗糙,但却方便得多的技术。然而,多个线程不能安全地共享一个 JDBC Connection。如清单 3 所示,通过使用“单子”中的 ThreadLocal,我们就能让我们的程序中的任何类容易地获取每线程 Connection 的一个引用。这样,我们可以认为 ThreadLocal 允许我们创建每线程单子。

例:把一个 JDBC 连接存储到一个每线程 Singleton 中

public class ConnectionDispenser {

private static class ThreadLocalConnection extends ThreadLocal {

public Object initialValue() {

return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());

}

}

private ThreadLocalConnection conn = new ThreadLocalConnection();

public static Connection getConnection() {

return (Connection) conn.get();

}

}

注意:

理论上来说,ThreadLocal是的确是相对于每个线程,每个线程会有自己的ThreadLocal。但是上面已经讲到,一般的应用服务器都会维护一套线程池。因此,不同用户访问,可能会接受到同样的线程。因此,在做基于TheadLocal时,需要谨慎,避免出现ThreadLocal变量的缓存,导致其他线程访问到本线程变量。

Servlet3中的AsyncContext异步和多线程异步有什么区别

public class ListServlet extends HttpServlet

{

private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException

{

response.getWriter().println(“I am begin output !”);

response.getWriter().flush();

//方式一
AsyncContext async = request.startAsync();
new AsyncOutput(async).start();

//方式二
new ThreadOutput(response).start();

response.getWriter().println("I am finash output !");
response.getWriter().flush();
}


}

class AsyncOutput extends Thread

{

private AsyncContext async;

public AsyncOutput(AsyncContext async)

{

this.async = async;

}

public void run()

{

try

{

Thread.sleep(3000);

async.getResponse().getWriter().println(“I was three minutes late !”);

async.getResponse().getWriter().flush();

}catch(Exception e)

{

e.printStackTrace();

}

}

}

class ThreadOutput extends Thread

{

private HttpServletResponse response;

public ThreadOutput(HttpServletResponse response)

{

this.response = response;

}

public void run()

{

try

{

Thread.sleep(3000);

response.getWriter().println(“I was three minutes late !”);

response.getWriter().flush();

}catch(Exception e)

{

e.printStackTrace();

}

}

}

AsyncContext不是让你异步输出,而是让你同步输出,但是解放服务器端的线程使用,使用AsyncContext的时候,对于浏览器来说,他们是同步在等待输出的,但是对于服务器端来说,处理此请求的线程并没有卡在那里等待,则是把当前的处理转为线程池处理了,关键就在于线程池,服务器端会起一个线程池去服务那些需要异步处理的请求,而如果你自己每次请求去起一个线程处理的话,这就有可能会耗大量的线程。

你目前对AsyncContext 的使用并不是最佳实践,实际上应该这样使用:

Java代码 收藏代码

final AsyncContext asyncContext = request.getAsyncContext();

asyncContext.addListener(new AsyncListener() {

@Override

public void onComplete(AsyncEvent event) throws IOException {

//在这里处理正常结束的逻辑

}

@Override
public void onTimeout(AsyncEvent event) throws IOException {
//在这里处理超时的逻辑
}

@Override
public void onError(AsyncEvent event) throws IOException {
//在这里处理出错的逻辑
}

@Override
public void onStartAsync(AsyncEvent event) throws IOException {
//在这里处理开始异步线程的逻辑
}
});
//设置超时的时间,到了时间以后,会回调onTimeout的方法
asyncContext.setTimeout(10000L);
//在这里启动,传入一个Runnable对象,服务器会把此Runnable对象放在线程池里面执行
asyncContext.start(new Runnable() {
@Override
public void run() {
//在这里做耗时的操作,如果做完,则调用complete方法通知回调,异步处理结束了
asyncContext.complete();
}
});


使用异步 Servlet 处理挂起线程

作者:Francesco Marchioni

12/04/2007

摘要

BEA WebLogic Server 9.2及以上版本将公开一个Abstract Asynchronous Servlet类,可用于解除接收servlet请求与发送其响应之间的耦合。该类还提供了一个Future Response Servlet,用于支持服务器使用一个不同的线程(而不是处理传入请求的线程)来处理servlet响应。传统servlet模型的这两个扩展都可以避免挂起线程并将长时间运行的作业与servlet范例集成在一起。本文将介绍这两个特性,同时将提供一些示例。

简介

是否希望通过在Web应用程序中控制响应时间来实现服务质量(QoS)?从最简单的形式来说,服务质量需求就是将响应时间控制在特定的时间(秒)内。如果无法满足这一需求,则会提供一条有意义的错误消息。

传统的servlet线程模式相当简单:应用服务器分配特定数量的线程,并提供给Web应用程序使用(参见图1)。当新请求传入并可供服务使用时,应用程序将分配一个线程。从此之后,servlet线程将一直占用内存池,直到它完成所有任务。

Traditional servlet threading model

图1. 传统servlet线程模式



如果servlet需要运行一个长时间的任务,则会造成一些问题。 要解决这一问题,最常用的方法是使用JMS或Message Driven Bean解除Web请求与长时间运行后台进程之间的耦合。这样便可解决不需要将处理结果立即返回响应的情况。

这听上去像是一个“Fire-and-forget”场景。但是,如果需要向客户机返回一些数据,那么servlet线程极有可能会成为挂起线程。

问题

在大多数情况下,挂起线程的意思就是在联系后台系统获取发送请求所需要的数据时受到了阻塞。其典型场景就是通过JDBC连接远程数据库。

问题的关键在于,如果所需资源速度变慢或完全不可用,则需要确保应用程序知道如何处理这一情况。大多数情况下,这并不是什么问题,因为超时套接字到远程数据库服务的代码都由数据源和JDBC驱动程序处理。但是,在需要手动编写超时策略时,该场景将变得极为复杂。

许多程序员都害怕处理网络超时。最常见的问题是,将没有超时支持的单线程网络客户机扩展为复杂的多线程时,每个单独的线程都需要测试网络超时,并且阻塞线程与主应用程序之间需要某种形式的通知流程。

本文将介绍如何使用BEA WebLogic Server的未来响应模型(Future Response Model)来编写能够有效处理超时的Java Web应用程序。这一过程非常简单。通过解除响应与传入请求及超时无响应请求之间的耦合,该模型还可以防止挂起线程。为避免这种线程挂起场景,WebLogic Server提供了两个类专门用于异步处理HTTP请求,其原理是解除响应与处理传入请求的线程之间的耦合。以下部分将详细介绍这两个类。

抽象异步Servlet类

AbstractAsyncServlet类的实现将解除接收servlet请求与发送响应之间的耦合。我们实现Abstract Asynchronous Servlet类的方法是扩展 weblogic.servlet.http.AbstractAsyncServlet 类。以下是需要实现的方法:

public boolean doRequest(RequestResponseKey rrk): 该方法是联系servlet的初始点。它接受的输入参数为RequestResponseKey类,RequestResponseKey类是传统servlet请求的包装器。下文将会介绍,我们可以使用请求判断servlet是否响应。

public void doResponse (RequestResponseKey rrk, Object context): 该方法将处理servlet响应。正如本文所述,初始线程并不需要处理此方法。

public void doTimeout (RequestResponseKey rrk): 如果未在特定时间段内发送servlet响应,则服务器将会触发doTimeout()方法。

Servlet类还提供了一个静态方法:

static void notify(RequestResponseKey rrk, Object context): 调用此方法将通知服务器应该向键rrk发送一个响应。

只实现抽象类还不足以实现解耦。解除请求与响应之间的耦合可以通过doRequest()方法中的 TimerListener 类来实现。可以使用工厂模式获得TimerListener类的一个新实例(实际上是通过TimerManagerFactory)。TimerListener线程可以针对AbstractAsyncServlet实例调用notify()方法,从而最终触发响应的发送。以下示例将演示如何创建这种计时器:

TimerManagerFactory.getTimerManagerFactory().

getDefaultTimerManager().schedule(new TimerListener() {

public void timerExpired(weblogic.timers.Timer arg0) {

try {

// This will trigger delivery of the response.

AbstractAsyncServlet.

notify(rrk, null);

}

catch (Exception e)

{

e.printStackTrace();

}

}

}, 1000);

此处的关键在于,如果该类位于doRequest()方法中,则需要等到计时器到期并调用notify()方法时才会发送响应。反过来,notify()将调用doResponse()方法。如果未在指定时间内调用notify()方法,则会调用doTimeout()方法。

图2展示了客户机与服务器之间的交互流程图。



Request/response decoupling using AbstractAsyncServlet

图2.使用AbstractAsyncServlet实现请求/响应解耦

我们来看看该模式的典型使用。其中一个场景为,服务质量应用程序需要在特定时间内向客户机发送一个响应消息(正如前面如述)。但是,即便是不同的用例(如延时的请求处理)也可以为我们带来好处:试想某个应用程序需要访问远程资源(比如说远程DB链接)或CPU密集算法。在此类场景中,延时处理请求(固定时间量)可以帮助防止过多同时访问。到目前为此,惟一的选择便是等待使用有价值的线程。

下面将演示该方法的应用示例。

Abstract Asynchronous Servlet的应用示例

假设,某个应用程序的作用是输出股票交易报价。我们将使用JCA接口获取股票价值,而该接口用于访问某个银行系统。用户需要在几秒钟之内获取反馈。但是,如果数千名用户在同一时间段时连接系统,则响应极有可能会延时,并且最坏的情况将导致HTTP连接超时。

不过所造成的损害远不止于此:如果用户发现该站点无法响应,则会单击刷新按钮,大量的新请求将会淹没应用服务器。

需要采取相同的措施来制止这一问题。如果后台资源受到阻塞,则无法满足用户的请求。但是,如果在收集最新的股票代码时就将其存入缓存,那么就可以在超时回调之前生成此信息,同时指出数据采集的时间。在本例中,我们将超时设置为2500毫秒。如果在此时间内未生成响应,则会调用doTimeout()方法。图3显示了其体系结构。



doTimeout callback

图3. 未及时通知servlet时将会触发doTimeout()回调

此servlet的详细代码如下所示:

public class AsyncServlet extends AbstractAsyncServlet {

// Need JDK 1.5

Hashtable
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  servlet