When Java EE 6 was announced with a new servlet specification i.e Servlet 3.0 – Asynchronous servlet was a very important feature added to the new specification. Suppose your servlet does a long running task like making a JDBC call or invoking a web service, such operations generally take up a lot of CPU time leading to keeping a Thread engaged for the same. This Thread allocated for a request is blocked waiting for the result of the long operation and hence can lead to resource starvation when many such requests are engaged by the server.
This and other problems which come with servlet handling long running tasks can be mitigated by using Asynchronous servlet. An asynchronous servlet releases the Thread back to the server pool and lets the long running task to proceed in the back ground. This long running task takes with it a context object which can be used to intimate the server of the availability of the result and this result can then be relayed back to the client from the server. Such process of server pushing the data to the client is called “Server Push” or “Comet” which is complimentary to “Ajax” which is a client side pull.
In this post I will show you how to create a Asynchronous Servlet and then make an Ajax call from the JSP to this servlet. And the JSP will display the output from the servlet. But before I go into that let me list out the classes which are used for creating Asynchronous servlet so that we have an idea of these classes before looking into the code.
Classes used for creating Asynchronous Servlet
AsyncContext: From the Javadoc:
Class representing the execution context for an asynchronous operation that was initiated on a ServletRequest.
An AsyncContext is created and initialized by a call to ServletRequest#startAsync() or ServletRequest#startAsync(ServletRequest, ServletResponse). Repeated invocations of these methods will return the same AsyncContext instance, reinitialized as appropriate.
AsyncListener: From the Javadoc:
Listener that will be notified in the event that an asynchronous operation initiated on a ServletRequest to which the listener had been added has completed, timed out, or resulted in an error.
The AsyncContext object holds the state of the request and response. Either the request and response object is taken from the current context or one can provide their request and response objects while initiating the asynchronous operation.
Creating the Long running task
Lets now create a long running task which has to be invoked from our servlet. This long running task implements Runnable so that it can be handed over to a Thread pool to be executed. Also this task takes in a AsyncContext object which is provided by the servlet. Lets look at the code for our long running task:
import java.io.IOException; import java.io.PrintWriter; import javax.servlet.AsyncContext; /** * Long running task which is to be invoked by our servlet. */ public class DemoAsyncService implements Runnable { /** * The AsyncContext object for getting the request and response * objects. Provided by the servlet. */ private AsyncContext aContext; public DemoAsyncService(AsyncContext aContext) { this.aContext = aContext; } @Override public void run() { PrintWriter out = null; try { //Retrieve the type parameter from the request. This is passed in the URL. String typeParam = aContext.getRequest().getParameter("type"); //Retrieve the counter paramter from the request. This is passed in the URL. String counter = aContext.getRequest().getParameter("counter"); System.out.println("Starting the long running task: Type="+typeParam+", Counter: "+counter); out = aContext.getResponse().getWriter(); //Sleeping the thread so as to mimic long running task. Thread.sleep(5000); /** * Doing some operation based on the type parameter. */ switch (typeParam) { case "1": out.println("This process invoked for "+ counter +" times."); break; case "2": out.println("Some other process invoked for "+ counter +" times."); break; default: out.println("Ok... nothing asked of."); break; } System.out.println("Done processing the long running task: Type="+typeParam+", Counter: "+counter); /** * Intimating the Web server that the asynchronous task is complete and the * response to be sent to the client. */ aContext.complete(); } catch (IOException |InterruptedException ex) { ex.printStackTrace(); } finally{ out.close(); } } }
The important parts in the above code have been highlighted- namely line 25, line 28, line 32, line 58. In Line 25 and Line 28 we make use of the AsyncContext object to retrieve data from the request object i.e HttpServletRequest object. In Line 32 we make use of the same AsyncContext object to obtain the output stream from the response object i.e HttpServletResponse object. The Line 58 is a bit interesting as this is the main part of the asynchronous processing. This line says to the web server that the asynchronous processing is completed and the response can be sent back to the client. The other way to let this happen is by invoking: aContext.dispatch()
method.
Creating the Asynchronous servlet
Now that we have a long running task to be invoked, its time to write our Asynchronous servlet. Asynchronous servlet is not much different from the usual servlet. To let the application server know that the servlet is asynchronous we have to set the asyncSupported
attribute in the @WebServlet
annotation to true
. Or if you are using a Deployment descriptor i.e web.xml
file to register your servlet then by adding tag within the servlet registration xml code we can let the server know that the given servlet is asynchronous.
Once we have done the identification for the asynchronous servlet, we need to get the AsyncContext object and then fire the long running task from within the servlet. When we fire the asynchronous operation we can register a callback which will be invoked as and when our operation is completed or timed out or runs into some error. Lets look at the code for the servlet:
/** * Our Asynchronous servlet with URL pattern = /asyncHello */ @WebServlet(name = "DemoAsyncServlet", urlPatterns = {"/asyncHello"}, asyncSupported = true) public class DemoAsyncServlet extends HttpServlet { /** * Processes requests for both HTTP * <code>GET</code> and * <code>POST</code> methods. * * @param request servlet request * @param response servlet response * @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs */ protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("Async Servlet with thread: " + Thread.currentThread().toString()); //Starting the asynchronous handling and obtaining the AsyncContext object. AsyncContext ac = request.startAsync(); //Registering a listener with the AsyncContext object to listen to events //from the AsyncContext object. ac.addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { System.out.println("Async complete"); } @Override public void onTimeout(AsyncEvent event) throws IOException { System.out.println("Timed out..."); } @Override public void onError(AsyncEvent event) throws IOException { System.out.println("Error..."); } @Override public void onStartAsync(AsyncEvent event) throws IOException { System.out.println("Starting async..."); } }); /** * Scheduling our Long running process using an inbuilt ThreadPool. Note * that we are passing the AsyncContext object to our long running process: * DemoAsyncService. */ ScheduledThreadPoolExecutor executer = new ScheduledThreadPoolExecutor(10); executer.execute(new DemoAsyncService(ac)); System.out.println("Servlet completed request handling"); } /** * Handles the HTTP * <code>GET</code> method. * * @param request servlet request * @param response servlet response * @throws ServletException if a servlet-specific error occurs * @throws IOException if an I/O error occurs */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } }
In the above code the interesting pieces of code are: Line 6, Line 25, Line 29, Line 57. In Line 6 we are declaring the servlet to be an asynchronous one. In Line 25 and Line 29 we are setting up the asynchronous context and registering an listener respectively. And in Line 57 we are launching our long running process from the servlet.
Invoking the Asynchronous servlet from the JSP
Lets go ahead and make an Ajax request to our asynchronous servlet. In our JSP we will have 2 buttons which on clicking them will updated the UI with some data from the server. Lets look at the JSP code for asyncSample.jsp:
<%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>JSP Page</title> <script src="asyncSample.js"></script> </head> <body> <h1>Hello World!</h1> <input type="button" id="ajaxBt1" value="Update data" /> <br/> <div id="req1Response"></div> <input type="button" id="ajaxBt2" value="Update some other data" /> <div id="req2Response"></div> </body> </html>
And we do the event handling binding and setting up the Ajax calls in a different Javascript file: asyncSample.js:
window.onload = setupEventHandlers; //Counters to record the number of times the buttons were clicked. var counter1; var counter2; //setting up the event handlers for the button function setupEventHandlers() { counter1 = 1; counter2 = 1; var ajaxBtn1 = document.getElementById("ajaxBt1"); var ajaxBtn2 = document.getElementById("ajaxBt2"); ajaxBtn1.onclick = handleAjaxBtn1; ajaxBtn2.onclick = handleAjaxBtn2; } //Event handlers for the buttons function handleAjaxBtn1(){ var xmlhrObj = new XMLHttpRequest(); xmlhrObj.open("GET", "http://localhost:8080/DemoApplication/asyncHello?type=1&counter="+counter1, true); counter1++; xmlhrObj.onreadystatechange = function(){ if(xmlhrObj.readyState == 4 && xmlhrObj.status == 200){ var div1 = document.getElementById("req1Response"); div1.innerHTML = xmlhrObj.responseText; } }; xmlhrObj.send(); } function handleAjaxBtn2(){ var xmlhrObj = new XMLHttpRequest(); xmlhrObj.open("GET", "http://localhost:8080/DemoApplication/asyncHello?type=2&counter="+counter2, true); counter2++; xmlhrObj.onreadystatechange = function(){ if(xmlhrObj.readyState == 4 && xmlhrObj.status == 200){ var div2 = document.getElementById("req2Response"); div2.innerHTML = xmlhrObj.responseText; } }; xmlhrObj.send(); }
The above code does the following:
1. Fetches the buttons declared in the JSP using their IDs.
2. Registers the click handlers for these buttons.
3. The click handlers then make Ajax requests to our servlet.
4. The ajax callbacks then update the UI with the output sent from the server.
Note: I am using Netbeans IDE 7.3.1 and Java EE 7 with Glassfish 4. This code can run on Java EE 6 as well. Once all the above are completed, we can deploy the application to app server and launch it in the browser using the: http://localhost:8080/DemoApplication/asyncSample.jsp URL.
You should see the below on your browser page on opening the URL for the first time:
And once you click on different buttons you would see the UI changing to:
Hope you like the article. It is one of the interesting and significant feature introduced with Servlets 3.0. In the future articles, I would continue writing the various topics on Servlets 3.0 and Java EE 6.0. You can receive the latest updates on our blog by connecting to our social network profiles.