Servlet 3.0中的异步处理支持

来源https://www.javaworld.com/article/2077995/java-concurrency/java-concurrency-asynchronous-processing-support-in-servlet-3-0.html

即使在现代基于UI组件的Web框架和Web服务技术中嵌入了中级API,传入的Servlet 3.0规范(JSR 315)将对Java Web应用程序开发产生突破性的影响。作者Xinyu Liu详细解释了为什么异步处理是定义Web 2.0的协作多用户应用程序的基础。他还总结了Servlet 3.0的其他增强功能,例如易于配置和可插拔性。

Java Servlet规范是大多数服务器端Java Web技术的共同特征,包括JavaServer Pages(JSP),JavaServer Faces(JSF),众多Web框架,SOAP和RESTful Web服务API以及新闻源。运行在这些技术下面的servlet使它们在所有Java Web服务器(servlet容器)之间都可以移植。对这种广泛接受的用于处理HTTP通信的API的任何建议的更改将潜在地影响所有附属的服务器端Web技术。

即将推出的Servlet 3.0规范于2009年1月通过公开审查,是一个重要的新功能,可以改变Java Web开发人员的生活。以下是您可以在Servlet 3.0中预期的列表:

  • 异步支持
  • 易于配置
  • 可插拔
  • 对现有API的增强

异步支持是Servlet 3.0最重要的增强功能,旨在使Ajax应用程序的服务器端处理更加高效。在本文中,我将重点介绍Servlet 3.0中的异步支持,首先解释支持异步支持的连接和线程消耗问题。然后,我将解释现实世界的应用程序如何在服务器推送实现(如Comet或反向Ajax)中使用异步处理。最后,我将介绍Servlet 3.0的其他增强功能,例如可插拔性和易于配置,让您对Servlet 3.0的良好印象及其对Java Web开发的影响。

异步支持:背景概念

Web 2.0技术大大改变了Web客户端(如浏览器)和Web服务器之间的流量配置。Servlet 3.0中引入的异步支持旨在应对这一新挑战。为了了解异步处理的重要性,我们首先考虑HTTP通信的演进。

HTTP 1.0到HTTP 1.1

HTTP 1.1标准的一个重大改进是持续连接在HTTP 1.0中,Web客户端和服务器之间的连接在单个请求/响应周期后关闭。在HTTP 1.1中,连接保持活动并重复使用多个请求。持久连接可以明显降低通信滞后,因为客户端在每个请求后不需要重新协商TCP连接。

每个连接线程

了解如何使Web服务器更具可扩展性是供应商面临的一个持续挑战。线程每HTTP连接,这是基于HTTP 1.1的持续连接,是供应商采用的常见解决方案。在此策略下,客户端和服务器之间的每个HTTP连接与服务器端的一个线程相关联。线程从服务器管理的线程池分配。一旦连接关闭,专用线程被回收回到池中,并准备提供其他任务。根据硬件配置,此方法可扩展到大量并发连接。高性能Web服务器的实验产生了数值结果,显示内存消耗几乎与HTTP连接数成正比。原因是线程在内存使用方面相对较贵。配置有固定线程的服务器可能会受到影响线程饥饿问题,一旦所有的线程被采取,新的客户端的请求被拒绝。

另一方面,对于许多网站,用户仅偶尔从服务器请求页面。这被称为逐页模型。大多数时候连接线程空闲,这是浪费资源的。

每个请求的线程

由于Java 4的Java平台(NIO)软件包的新I / O API中引入的非阻塞I / O功能,持久的HTTP连接不需要线程不断附加到它。只有当请求被处理时,才能将线程分配给连接。当连接在请求之间空闲时,线程可以被回收,并且连接被放置在集中式的NIO选择集中以检测新的请求而不消耗单独的线程。这个模型,根据请求调用线程,可能允许Web服务器使用固定数量的线程来处理越来越多的用户连接。使用相同的硬件配置,以此模式运行的Web服务器比线程每连接模式更好。今天,流行的Web服务器(包括Tomcat,Jetty,GlassFish(Grizzly),WebLogic和WebSphere)都通过Java NIO对每个请求使用线程。对于应用程序开发人员来说,好消息是Web服务器以隐藏的方式实现非阻塞I / O,而不会通过servlet API暴露任何应用程序。

迎接Ajax挑战

通过更灵敏的界面提供更丰富的用户体验,越来越多的Web应用程序使用Ajax。Ajax应用程序的用户与Web服务器的交互频率要高于逐页模型。与普通用户请求不同,Ajax请求可以由一个客户端经常发送到服务器。此外,客户端和客户端上运行的脚本都可以定期轮询Web服务器进行更新。更多同时的请求会导致更多的线程被消耗,这在很大程度上消除了每个请求线程的优点。

运行缓慢,资源有限

一些运行缓慢的后端例程恶化了这种情况。例如,请求可能被耗尽的JDBC连接池或低吞吐量Web服务端点阻止。在资源可用之前,线程可能会长时间停留在待处理的请求中。最好将请求放在等待可用资源的集中式队列中并回收该线程。这有效地节省了请求线程的数量以匹配慢速运行的后端例程的容量。它还表明,在请求处理过程中的某个时刻(当请求存储在队列中时),根本就不会消耗线程。Servlet 3.0中的异步支持旨在通过通用和便携式方法来实现此场景,无论是否使用Ajax。清单1显示了它的工作原理。

清单1.限制对资源的访问
@WebServlet(name="myServlet", urlPatterns={"/slowprocess"}, asyncSupported=true)
public class MyServlet extends HttpServlet {
   
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        AsyncContext aCtx = request.startAsync(request, response); 
        ServletContext appScope = request.getServletContext();
        ((Queue<AsyncContext>)appScope.getAttribute("slowWebServiceJobQueue")).add(aCtx);
    }
}

@WebServletContextListener
public class SlowWebService implements ServletContextListener {

    public void contextInitialized(ServletContextEvent sce) {
        Queue<AsyncContext> jobQueue = new ConcurrentLinkedQueue<AsyncContext>();
        sce.getServletContext().setAttribute("slowWebServiceJobQueue", jobQueue);
        // pool size matching Web services capacity
        Executor executor = Executors.newFixedThreadPool(10);
        while(true)
        {
            if(!jobQueue.isEmpty())
            {
                final AsyncContext aCtx = jobQueue.poll();
                executor.execute(new Runnable(){
                    public void run() {
                        ServletRequest request = aCtx.getRequest();
                        // get parameteres
                        // invoke a Web service endpoint
                        // set results
                        aCtx.forward("/result.jsp");
                    }                    
                });             
            }
        }
    }
    
    public void contextDestroyed(ServletContextEvent sce) {
    }
}

asyncSupported属性设置为true方法退出时提交响应对象调用startAsync()返回AsyncContext缓存请求/响应对象对的对象。AsyncContext然后对象存储在应用程序范围的队列中。没有任何延迟,该doGet()方法返回,并且原始请求线程被回收。在该ServletContextListener对象中,在应用程序启动过程中启动的单独线程监视队列,并在资源可用时继续请求处理。处理请求后,您可以选择调用ServletResponse.getWriter().print(...),然后complete()提交响应,或调用forward()将流引导到JSP页面以显示结果。请注意,JSP页面是带有的asyncSupported属性默认为false

另外,Servlet 3.0中的类AsyncEventAsynListener类可以让开发人员精心控制异步生命周期事件。您可以AsynListener通过该ServletRequest.addAsyncListener()方法注册startAsync()请求方法被调用之后,一旦异步操作完成或超时,就将a AsyncEvent发送到注册AsyncListenerAsyncEvent还包含相同的请求和响应对象作为AsyncContext对象。

服务器推送

Servlet 3.0异步功能的一个更有趣和最重要的用例是服务器推送GTalk,一个允许GMail用户在线聊天的小部件,是服务器推送的一个例子。GTalk不会经常轮询服务器来检查是否有新消息可用于显示。而是等待服务器推回新消息。这种方法有两个明显的优点:低延迟通信,无需发送请求,不浪费服务器资源和网络带宽。

即使同时处理来自同一用户的其他请求,Ajax也允许用户与页面进行交互。常见的用例是让浏览器定期轮询服务器来更新状态更改,而不会中断用户。然而,高轮询频率浪费服务器资源和网络带宽。如果服务器可以主动将数据推送到浏览器 - 换句话说,在事件(状态更改)上向客户端传递异步消息 - Ajax应用程序将执行得更好,并节省宝贵的服务器和网络资源。

HTTP协议是一个请求/响应协议。客户端向服务器发送请求消息,服务器用响应消息进行回复。服务器无法发起与客户端的连接或向客户端发送意外的消息。HTTP协议的这一方面似乎使得服务器推送变得不可能。但是,已经设计了几种巧妙的技术来规避这种限制:

  • 服务流(流)允许服务器在事件发生时向客户端发送消息,而不需要客户端的显式请求。在现实世界的实现中,客户机通过请求发起与服务器的连接,并且每次发生服务器端事件时,响应返回位; 响应(理论上)永远持续。客户端JavaScript可以解释这些位和块,并通过浏览器的增量渲染功能进行显示。
  • 长轮询,也称为异步轮询,是纯服务器推送和客户端拉动的混合。它基于使用基于主题的发布订阅方案的Bayeux协议。在流式传输中,客户端通过发送请求来订阅服务器上的连接通道。服务器保存请求并等待事件发生。一旦事件发生(或者在预定义的超时之后),将向客户端发送完整的响应消息。收到响应后,客户端立即发送新请求。然后,服务器几乎总是有一个出色的请求,它可以用来传送数据以响应服务器端事件。浏览器端的漫游比流式传输更容易实现。
  • 被动捎带:当服务器有更新发送时,它等待下一次浏览器发出请求,然后发送其更新以及浏览器期望的响应。

使用Ajax实现的服务流和长轮询称为Comet或反向Ajax(一些开发人员将所有的交互技术称为反向Ajax,包括常规投票,彗星和背驮式)。

Ajax提高了单用户的响应能力。像Comet这样的服务器推送技术可以提高协作式,多用户应用程序的应用程序响应能力,而无需常规轮询的开销。

服务器推送技术的客户端方面,例如隐藏iframes,XMLHttpRequest流媒体,以及促进异步通信的一些Dojo和jQuery库 - 不在本文的范围之内。相反,我们的兴趣在于服务器端,具体来说,Servlet 3.0规范如何帮助用服务器推送实现交互式应用程序。

高效线程使用:通用解决方案

Comet(流和长轮询)总是占用一个通道,等待服务器推回状态更改。客户端不能重复使用相同的频道发送普通请求。因此,Comet应用程序通常使用每个客户端两个连接。如果应用程序处于线程请求模式,则会将一个未完成的线程与每个Comet连接相关联,从而导致比用户数更多的线程。使用线程每请求方法的Comet应用程序只能以巨大的线程消耗的昂贵的费用来扩展。

Servlet 2.5规范中缺少异步支持,导致服务器厂商通过编写专门的类和接口来设计解决方案,从而将可扩展的Comet编程扩展到Servlet 2.5 API中。例如,Tomcat有一个CometProcessor类,Jetty 6有Continuations,Grizzly有一个CometEngine类。使用Servlet 3.0,在编写可扩展Comet应用程序时,不再需要牺牲便携性。清单1所示的异步功能整合了来自多个供应商的优秀设计思路,并为Comet应用程序中高效的线程使用提供了一个通用的便携式解决方案。

我们来看一个真实世界的例子 - 一个在线拍卖网站。当买家在网上投标一个项目时,它会让他们觉得必须保持刷新页面才能看到最新/最高出价。这是一个麻烦,特别是当招标即将结束时。使用服务器推送技术,不需要页面刷新或高频Ajax轮询。如果出现新的出价,服务器只需将项目的出价提交给注册的客户,如清单2所示。

清单2.拍卖观看
@WebServlet(name="myServlet", urlPatterns={"/auctionservice"}, asyncSupported=true)
public class MyServlet extends HttpServlet {
   
   // track bid prices
   public void doGet(HttpServletRequest request, HttpServletResponse response) {
      AsyncContext aCtx = request.startAsync(request, response); 
      // This could be a cluser-wide cache.
      ServletContext appScope = request.getServletContext();    
      Map<String, List<AsyncContext>> aucWatchers = (Map<String, List<AsyncContext>>)appScope.getAttribute("aucWatchers");
      List<AsyncContext> watchers = (List<AsyncContext>)aucWatchers.get(request.getParameter("auctionId"));
      watchers.add(aCtx); // register a watcher
   }

   // place a bid
   public void doPost(HttpServletRequest request, HttpServletResponse response) {
      // run in a transactional context 
      // save a new bid
      AsyncContext aCtx = request.startAsync(request, response); 
      ServletContext appScope = request.getServletContext(); 
      Queue<Bid> aucBids = (Queue<Bid>)appScope.getAttribute("aucBids");
      aucBids.add((Bid)request.getAttribute("bid"));  // a new bid event is placed queued.  
   }
}

@WebServletContextListener
public class BidPushService implements ServletContextListener{

   public void contextInitialized(ServletContextEvent sce) {   
      Map<String, List<AsyncContext>> aucWatchers = new HashMap<String, List<AsyncContext>>();
      sce.getServletContext().setAttribute("aucWatchers", aucWatchers);
      // store new bids not published yet
      Queue<Bid> aucBids = new ConcurrentLinkedQueue<Bid>();
      sce.getServletContext().setAttribute("aucBids", aucBids);
        
      Executor bidExecutor = Executors.newCachedThreadPool(); 
      final Executor watcherExecutor = Executors.newCachedThreadPool();
      while(true)
      {        
         if(!aucBids.isEmpty()) // There are unpublished new bid events.
         {
            final Bid bid = aucBids.poll();
            bidExecutor.execute(new Runnable(){
               public void run() {
                  List<AsyncContext> watchers = aucWatchers.get(bid.getAuctionId()); 
                  for(final AsyncContext aCtx : watchers)
                  {
                     watcherExecutor.execute(new Runnable(){
                        public void run() {
                           // publish a new bid event to a watcher
                           aCtx.getResponse().getWriter().print("A new bid on the item was placed. The current price ..., next bid price is ...");
                        };
                     });
                  }                           
               }
            });
         }
      }
   }
    
   public void contextDestroyed(ServletContextEvent sce) {
   }
}

清单2是非常不言自明的。对于所有的拍卖观察者,未提交的长寿命响应对象都存储在AsynchContext缓存在应用程序范围内的对象中Map启动请求/响应循环的原始线程然后返回到线程池,并准备提供其他任务。每当出现新的出价时,在ServletContextListener对象中发起的线程从队列中取出,然后将事件推送到为同一拍卖注册的所有观察者(存储的响应对象)。新的出价数据被流传输到连接的浏览器,而不被客户明确要求。这些响应对象未提交用于流式传输,或者为长时间轮询提交。如您所见,在服务器端没有出现任何出价事件时,不会为观察者使用任何线程。

具有Comet功能的新型便携式自定义UI组件(小部件)可以在诸如JSF和Wicket之类的框架下提供。通过Servlet 3.0中的注释和可插拔功能,我将在下一节讨论这些功能 - 这些小部件可以在任何配置下使用。对于应用程序开发人员来说,这肯定是个好消息。

Servlet 3.0中的其他增强功能

除了异步支持之外,Servlet 3.0规范还包括增强功能,使应用程序配置和开发变得更加容易。

易于配置

使用Servlet 3.0,您现在有三个配置Java Web应用程序的选项:注释,API和XML。在Java 5中引入的注释被广泛接受为Java源代码级别的元数据设施。通过注释,对XML配置的需求大大降低。新规范引入了几个方便的注释。the @WebServlet@ServletFilterand和@WebServletContextListenerannotations等同于它们在web.xml部署描述符文件中的相应标签

互补技术

适用于各种Java技术的一般规则是,配置集以编程方式优先于XML中的值,并且该XML始终覆盖注释。我认为XML和注释是补充技术。对于与特定Java类,方法,属性密切相关的相对静态的设置,我将使用源代码级的注释。我更喜欢使用XML来实现更加易变的全局设置,而不是与Java源代码绑定,以避免重新编译更改,或者当我无法访问Java源代码(因为类被包装在JAR文件中)时,或者当我需要覆盖注释中定义的设置。

与此同时,该规范增强了ServletContext与一些方法的类- ServletContext.addServletMapping(...)ServletContext.addFilter(...),例如-允许您以编程方式更改配置。注释和这些新方法作为配置选项的可用性意味着web.xml在Java Web存档(WAR)文件中不再需要一个文件。

可插拔

无论您现在用于Web开发的框架如何,您总是需要在Web应用程序的web.xml文件中添加具有特定设置的几个技术强制的servlet或过滤器更糟的是,如果您想要集成提供自定义GUI小部件或安全功能(如Spring Security)的技术扩展包,则必须添加更多的servlet,过滤器和参数。您的web.xml文件不断增长,并逐渐成为维护麻烦。

虽然Servlet 3.0中的注释使web.xml文件成为可选的,但有时候对于诸如增量升级的情况,XML仍然是可取的,以覆盖默认属性值或通过注释设置的值。为了提高框架和组件库的可插拔性,Servlet 3.0引入了一个新的XML <web-fragment>标签,用于指定web.xml可以包含在META-INF库或框架的JAR文件目录中的Web片段(描述为部分文件的组件列表当JAR文件加载并扫描作为整个Web应用程序的一部分时,Servlet容器将拾取并合并片段声明。

对现有API的增强

Servlet 3.0规范对servlet API进行了一些调整。例如,一个getServletContext方法被添加到ServletRequest类中作为方便方法。新版本的Cookie课程现在支持HTTPOnly不暴露于客户端脚本代码的Cookie,以减轻跨站点脚本攻击。

在Servlet 3.0规范的早期草案中提出了一些用于编程式登录/注销的方法作为安全增强功能。还值得一提的是,JSR专家组试图使新的servlet API在早期草案中更像POJO,但最终放弃了。毕竟,由于API对应用程序开发人员的影响最小,所以使POJO类似的中低级API不重要。

结论是

Servlet 3.0是一个主要的规范版本。最终版本将包含在Java EE 6中,该EE EE可能在2009年春季首次亮相。Servlet 3.0中的API增强功能,基于注释的配置和可插拔功能必将使Web应用程序开发变得更加容易。最重要的是,长期以来的异步功能统一了API,用于实现诸如Comet之类的服务器推送技术以及资源调节。构建在Servlet 3.0规范之上的Comet应用将比以往更加便携和可扩展。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值