Asynchronous processing support in Servlet 3.0

Why asynchronous processing is the new foundation of Web 2.0

1 2 Page 2
Page 2 of 2

Efficient thread use: A universal solution

Comet (streaming and long polling) always occupies a channel waiting on the server to push back state changes. The same channel can't be reused by the client to send ordinary requests. So Comet applications typically work with two connections per client. If the application stays in thread-per-request mode, an outstanding thread will be associated with each Comet connection, resulting in more threads than the number of users. Comet applications that use the thread-per-request approach can scale only at the prohibitive expense of huge thread consumption.

Lack of asynchronous support in the Servlet 2.5 specification has caused server vendors to devise workarounds by weaving proprietary classes and interfaces that promote scalable Comet programming into the Servlet 2.5 API. For example, Tomcat has a CometProcessor class, Jetty 6 has Continuations, and Grizzly has a CometEngine class. With Servlet 3.0, you no longer need to sacrifice portability when programming scalable Comet applications. The asynchronous feature shown in Listing 1 consolidates great design ideas from multiple vendors and presents a universal, portable solution for efficient thread usage in Comet applications.

Let's look at a real-world example -- an online auction Web site. When buyers bid for an item online, it annoys them that they must keep refreshing the page to see the latest/highest bid. This is a hassle especially when the bidding is about to end. With server-push technologies, no page refresh or high-frequency Ajax polling is needed. The server simply pushes the item's bid price to the registered clients whenever a new bid is placed, as shown in Listing 2.

Listing 2. Auction watching

@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) {
   }
}

Listing 2 is quite self-explanatory. For all the auction watchers, the uncommitted, long-lived response objects are stored in the AsynchContext objects, which are cached in an application-scoped Map. The original threads initiating the request/response loop are then returned to the thread pool and ready to serve other tasks. Whenever a new bid is placed, threads initiated in the ServletContextListener object pick it up from a queue, and then push the event to all the watchers (stored response objects) registered for the same auction. The new bid data is streamed to the connected browsers without being explicitly requested by the clients. These response objects are either uncommitted for streaming, or committed for long polling. As you can see, no threads are consumed for watchers when no bid event is happening on the sever side.

New portable custom UI components (widgets) with Comet ability can be delivered under frameworks such as JSF and Wicket. With annotations and pluggability in Servlet 3.0 -- features I'll discuss in the next section -- these widgets can be used off the shelf without any configuration. For application developers, this surely is great news.

Other enhancements in Servlet 3.0

In addition to asynchronous support, the Servlet 3.0 specification includes enhancements designed to make application configuration and development easier.

Ease of configuration

With Servlet 3.0, you now have three options for configuring a Java Web application: annotations, the API, and XML. Annotations, introduced in Java 5, are widely accepted as a metadata facility at the Java source-code level. With annotations, the need for XML configuration is drastically lowered. The new specification introduces several handy annotations. The @WebServlet, @ServletFilter, and @WebServletContextListener annotations are equivalent to their corresponding tags in the web.xml deployment descriptor file.

Complementary techniques

A general rule that applies to various Java technologies is that configurations set programmatically take precedence over values in XML, and that XML always overrides annotations. I think of XML and annotations as complementary techniques. For relatively static settings closely related to specific Java classes, methods, properties, I would use source-level annotations. I prefer to use XML for more volatile, global settings not tied to Java source code to avoid recompilation on changes, or when I don't have access to the Java source code (because classes are wrapped in a JAR file), or when I need to override the settings defined in annotations.

At the same time, the specification augments the ServletContext class with a few methods -- ServletContext.addServletMapping(...) and ServletContext.addFilter(...), for example -- that allow you to alter configurations programmatically. The availability of annotations and these new methods as configuration options means a web.xml file is no longer required in a Java Web archive (WAR) file.

Pluggability

Regardless of the framework you use now for Web development, you're always required to add several technology-mandated servlets or filters with specific settings in your Web application's web.xml file. Even worse, you must add yet more servlets, filters, and parameters if you want to incorporate technology extension packages offering custom GUI widgets or security features (such as Spring Security). Your web.xml file keeps growing and gradually becomes a maintenance hassle.

Although annotations in Servlet 3.0 make a web.xml file optional, XML sometimes is still desirable for cases such as incremental upgrades, to override either the default attribute values or the values set via annotations. To improve pluggability for frameworks and component libraries, Servlet 3.0 introduces a new XML <web-fragment> tag for specifying a Web fragment (a list of components described as a partial web.xml file ) that can be included in the META-INF directory of a library or framework's JAR file. Servlet containers will pick up and merge the fragment declarations when the JAR file is loaded and scanned as part of the entire Web application.

Enhancements to existing APIs

The Servlet 3.0 specification makes a number of adjustments to servlet APIs. For example, a getServletContext method is added to the ServletRequest class as a convenience method. The new version of the Cookie class now supports HTTPOnly cookies, which are not exposed to client-side scripting code, to mitigate cross-site scripting attacks.

A few methods for programmatic login/logout proposed as a security enhancement in an early draft of the Servlet 3.0 specification were removed. It's also worth mentioning that the JSR expert group tried to make the new servlet APIs more POJO-like in an early draft, but they ultimately gave up. After all, making mid-to-low level APIs POJO-like is unimportant because the API has minimal exposure to application developers.

In conclusion

Servlet 3.0 is a major specification release. The final version will be included in Java EE 6, which is likely to debut in the spring of 2009. The API enhancements, annotation-based configuration, and pluggability features in Servlet 3.0 will certainly make Web application development much easier. Most important, the long-desired asynchronous feature unifies the APIs for implementing server-push technologies such as Comet, as well as resource throttling. Comet applications built on top of the Servlet 3.0 specification will be more portable and scalable than ever.

Dr. Xinyu Liu is a Sun Microsystems certified enterprise architect working in a healthcare corporation and an IT consulting firm.

Learn more about this topic

More from JavaWorld

1 2 Page 2
Page 2 of 2