In this article, we will continue a discussion on asynchronous processing started in a previous article that introduced asynchronous and parallel processing Java using Executors, Futures, Callable Objects and the underlying thread model in Java 5 and 6.
While a stand alone Java application – without UI – is a rare thing in my world, a Java Web application certainly is not. And performance, especially perceived performance, is pretty important in that world. The first page load is the most important measure I suppose for what the user feels is the performance of the web application. The fact that after the initial load, additional elements can be loaded into the page – asynchronously – is fine. The intial page load and the browser’s indication that the load is done (and the hourglass disappears) is what is most important for the happiness of the user.
We will see three stages in this article, of a very simple web page. It is a JSF (JavaServer Faces) page, that contains some very simple elements of which one displays an ‘expensive’ value – a value that takes some time to get hold of. Maybe because a database query is involved or web service is called. Whatever the cause, this one element is expensive.
We will first load the page as is – with all elements loaded at once. In that case nothing is loaded into the browser until all values, including the expensive value that takes 10 seconds to retrieve, have been collected. The user sits and waits for over 10 seconds. Clearly the user won’t be thrilled.
The next step we take with our page is to have it load without the expensive value. The page loads quickly. Then an asynchronous AJAX call is performed to ask the server for the expensive value. When the call is received on the server, processing starts and after 10 seconds, the value is returned to the client and used to refresh the page. Ten seconds (plus the time needed for retrieving the page without the expensive value) after the page was loaded, all page contents are displayed. And the user perceives the performance as much better, as the initial page load is done much more rapidly.
The last step makes us of asynchronous processing at two levels: the AJAX level in the browser and the Java level in the server. Instead of only starting to work on calculating the expensive value when the AJAX request comes in, after the page was rendered, sent to the browser and loaded, the calculation is kicked off in a parallel thread during page rendering. The page renders like before, loads in the browser and then sends the AJAX request. But instead of starting the calculation when that request hits the server, the AJAX request reaches a Servlet that retrieves the already running task and waits for it to complete. This means that the value is calculated even faster – thanks to the processing that went on while the page being rendered, sent and loaded in the browser.
These three cases can be visualized like this:
In the third case as we can clearly see – in this exaggerated picture – the page loads as fast as in the second case and the expensive value is pasted into the page quicker than in the second case. So how is that implemented, you may wonder. Let’s take a look – first things first, so first the initial situation of case 1.
Case 1
A simple JSF page that contains various panelGrids with contents. One data value is calculated quickly (the fast value) and consists of the timestamp of returning the value.
The expensive value is marked in the red box. It took 10 seconds to calculate – and returns the timestamp that marks the end of the calculation. As I explained before, the page is loaded in its entirety, when all values have been calculated.
The underlying code is hardly interesting – though I will show it to demonstrate the difference later on. The JSF page:
... <h:panelGrid columns="1"> <h:panelGroup> <h:outputLabel value="The Fast Value "/> <h:outputText value="#{ Bean.fastValue}"/> </h:panelGroup> <h:panelGroup> <h:outputLabel value="The Value we have all Been waiting for...."/> <h:outputText value="#{ Bean.value}"/> </h:panelGroup> ...
And the referenced bean:
public class Bean2 { private String value; public Bean2() { } public void setValue(String value) { this.value = value; } public String getValue() { setValue(new Calculator().getValue()); return value; } public String getFastValue() { return new java.util.Date().toString(); } }
The Calculator class is extremely simple:
public class Calculator { public Calculator() { } public String getValue() { Date startTime = new java.util.Date(); System.out.println("++++ start Calculator.getValue - at "+startTime); try { // sleep 10 seconds to make it a really slow job Thread.sleep(10000); } catch (InterruptedException e) { } Date endTime = new java.util.Date(); System.out.println("++++ Job took " + new Double(0.001*(endTime.getTime() - startTime.getTime()))+ " seconds"); return "Value Created at "+new java.util.Date(); } }
The output of loading the page in case 1 is as follows:
++++ start Calculator.getValue - at Thu Feb 19 21:38:15 CET 2009 ++++ Job took 10.0 seconds
Case 2 – Post loading the synchronously calculated value
In this case, the page loads without the expensive value. Instead it shows the text Later…. here marked in the red box. Immediately after the page loads, the onLoad event fires and the associated method is invoked. This method brings up the alert and then makes an Asynchronous AJAX request to the server. It calls a Servlet that will in turn create an instance of the Calculator bean to calculate the value – which takes 10 seconds.
When the Servlet has received the synchronous response from the Calculator, it returns the response to the client that will then process it and use the value to update the page.
Note that the difference in timestamp between the fast value and the expensive value is more than 10 seconds – as the expensive value’s calculation is only started after the AJAX request has been received by the servlet.
The code in the page is somewhat changed from before:
<h:panelGrid id="grid" columns="1"> <h:panelGroup> <h:outputLabel value="The Fast Value "/> <h:outputText value="#{ Bean2.fastValue}"/> </h:panelGroup> <h:panelGroup id="pgDeferred"> <h:outputLabel value="The Value we have all Been waiting for...."/> <h:outputText id="deferredValue" value="#{ Bean2.deferredValue}"/> </h:panelGroup>
.... <f:verbatim> <![CDATA[ <script type="text/javascript" src="PostLoadResources.js"</script> <script src="ContentLoader.js" type="text/javascript"></script> <script type="text/javascript">addPostLoadResource('form:deferredValue', 'deferredvalueproviderservlet?asynch=no', false, null,null, 'POSTLOAD_SERVLET_LOADER');</script> ]]> </f:verbatim>
The deferred value is provided in Bean2:
public String getDeferredValue() { System.out.println("$$$$ Bean2.GetDeferredValue at "+new java.util.Date()); return "later...."; }
The servlet that is being called looks like this:
package nl.amis.view; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.*; import javax.servlet.http.*; public class DeferredValueProviderServlet extends HttpServlet { private static final String CONTENT_TYPE = "text/xml; charset=windows-1252"; private static final String DOC_TYPE = null; public void init(ServletConfig config) throws ServletException { super.init(config); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType(CONTENT_TYPE); PrintWriter out = response.getWriter(); writeContents(request, out); out.close(); } private void writeContents(HttpServletRequest request, PrintWriter out) { // start the job here and now; no benefit from pre-calculation/asynchronously started jobs Calculator calc = new Calculator(); out.println("(Result from Server) "+calc.getValue()); System.out.println("^^^^^^^^ Servlet: retrieved value from newly (sequentially) started Calculator - NOT leveraged asynch processing "); } } }
Its configuration in the web.xml:
<servlet> <servlet-name>DeferredValueProviderServlet</servlet-name> <servlet-class>nl.amis.view.DeferredValueProviderServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DeferredValueProviderServlet</servlet-name> <url-pattern>/deferredvalueproviderservlet</url-pattern> </servlet-mapping>
And the output in the JVM console when loading the page:
$$$$ Bean2.GetDeferredValue at Thu Feb 19 21:48:43 CET 2009 ++++ start Calculator.getValue - at Thu Feb 19 21:48:46 CET 2009 ++++ Job took 10.0 seconds ^^^^^^^^ Servlet: retrieved value from newly (sequentially) started Calculator - NOT leveraged asynch processing
Case 3 – Post loading asynchronously calculated results to the page
In the last case, the post loaded value is retrieved from a task that was started asynchronously when the page was first rendered. The period between the fast value and the expensive value is exactly 10 seconds.
The changes in the code are subtle. First in the page:
<h:panelGroup id="pgDeferred"> <h:outputLabel value="The Value we have all Been waiting for...."/> <h:outputText id="deferredValue" value="#{ Bean2.deferredValue}"/> </h:panelGroup> </h:panelGrid> </h:form> <f:verbatim> <![CDATA[ <script type="text/javascript" src="PostLoadResources.js"</script> <script src="ContentLoader.js" type="text/javascript"></script> <script type="text/javascript">addPostLoadResource('form:deferredValue', 'deferredvalueproviderservlet?asynch=yes', false, null,null, 'POSTLOAD_SERVLET_LOADER');</script> ]]> </f:verbatim> </body>
Then in the bean:
public String getDeferredValue() { System.out.println("$$$$ Bean2.GetDeferredValue at "+new java.util.Date()); readyAsynch=false; // have deferred value calculated by submitting task CalculatorMediator worker = new CalculatorMediator(); worker.setInvoker(this); coordinator.scheduleTask(worker); return "later...."; }
/** * This getter returns the value that this object has started to prepare when method getDeferredValue * was invoked. This method started asynchronous processing (set the readyAsynch boolean to false) and returned control. When the asynchronous * processing is complete, the Callable worker object calls the returnResult() method that in turn sets * the asynchValue member of this bean and sets the readyAsynch boolean to true. * * The method checks whether the readyAsynch boolean is true; if not, it will wait for a maximum of 15 seconds for the readyAsynch to be * toggeled to true (in steps of 1 second); as soon as the readyAsynch flag is set, the asynchValue is returned. After 15 seconds this method * times out and returns the string "Timed Out" * * @return */ public String getAsynchValue() { System.out.println("$$$$ Bean2.Get Asynch (deferred) value at "+new java.util.Date()); if (!readyAsynch) { for (int i=0;i<15;i++) { try { System.out.println("$$$$ ... getAsynchValue() - value not available; sleep 1 sec"); Thread.sleep(1000); } catch (InterruptedException e) {} if (readyAsynch) break; } } System.out.println("$$$$ Bean2 - asynch value available at "+new java.util.Date()); return readyAsynch?asynchValue:"Timed Out"; } /** * The callback method that is invoked by consumers of the CallbackInterface; typically a Callable object * that has been passed this (object instance) will return its value to this object using this method. * * @param result The result produced by the caller (typically the Callable object); we assume it is a String */ public void returnResult(Object result) { System.out.println("$$$$ Bean2.returnResult - Result Received: "+result+" at "+new java.util.Date()); asynchValue = (String)result; readyAsynch = true; } public void setCoordinator(TaskCoordinator coordinator) { this.coordinator = coordinator; } public TaskCoordinator getCoordinator() { return coordinator; }
The CalculatorMediator class used here implements the Callable interface:
public class CalculatorMediator implements Callable { private CallbackInterface invoker; public CalculatorMediator() { } public Object call() { invoker.returnResult(new Calculator().getValue()); return null; } public void setInvoker(CallbackInterface invoker) { this.invoker = invoker; } public CallbackInterface getInvoker() { return invoker; }
The TaskCoordinator class:
public class TaskCoordinator { private ExecutorService es = Executors.newFixedThreadPool(3); public TaskCoordinator() { } public Future scheduleTask(Callable worker) { System.out.println("@@@@@ TaskCoordintor (Application Scope) has received task to execute, at "+new java.util.Date()); final Future future = es.submit( worker); System.out.println("@@@@@ TaskCoordintor submitted to executor service, at "+new java.util.Date()); return future; } }
The managed beans in faces-config.xml:
<managed-bean> <managed-bean-name>Bean2</managed-bean-name> <managed-bean-class>nl.amis.view.Bean2</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> <managed-property> <property-name>coordinator</property-name> <property-class>nl.amis.view.TaskCoordinator</property-class> <value>#{TaskCoordinator}</value> </managed-property> </managed-bean> <managed-bean> <managed-bean-name>TaskCoordinator</managed-bean-name> <managed-bean-class>nl.amis.view.TaskCoordinator</managed-bean-class> <managed-bean-scope>application</managed-bean-scope> </managed-bean>
And finally in the Servlet:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType(CONTENT_TYPE); System.out.println("param asynch "+request.getParameter("asynch")); PrintWriter out = response.getWriter(); writeContents(request, out, request.getParameter("asynch")); out.close(); } private void writeContents(HttpServletRequest request, PrintWriter out, String asynch) { // retrieve value from JSF managed beans in Servlet: blogs.sun.com/chrisf/entry/retrieving_jsf_session_variables_in+servlet+retrieve+from+object+in+session if ("yes".equalsIgnoreCase(asynch)) {
// leverage the aysnchronously started bean2
Bean2 bean2 = (Bean2)request.getSession().getValue("Bean2");
out.println("(Result from Server) "+bean2.getAsynchValue());
System.out.println("^^^^^^^^ Servlet: retrieved value from bean2 - leveraged asynch processing ");
}
else { // start the job here and now; no benefit from pre-calculation/asynchronously started jobs Calculator calc = new Calculator(); out.println("(Result from Server) "+calc.getValue()); System.out.println("^^^^^^^^ Servlet: retrieved value from newly (sequentially) started Calculator - NOT leveraged asynch processing "); } }
The output of loading this page in the Console:
$$$$ Bean2.GetDeferredValue at Thu Feb 19 21:53:06 CET 2009 @@@@@ TaskCoordintor (Application Scope) has received task to execute, at Thu Feb 19 21:53:06 CET 2009 ++++ start Calculator.getValue - at Thu Feb 19 21:53:06 CET 2009 @@@@@ TaskCoordintor submitted to executor service, at Thu Feb 19 21:53:06 CET 2009 !!!!! Filter has worked its magic param asynch yes $$$$ Bean2.Get Asynch (deferred) value at Thu Feb 19 21:53:15 CET 2009 $$$$ ... getAsynchValue() - value not available; sleep 1 sec $$$$ ... getAsynchValue() - value not available; sleep 1 sec ++++ Job took 10.0 seconds $$$$ Bean2.returnResult - Result Received: Value Created at Thu Feb 19 21:53:16 CET 2009 at Thu Feb 19 21:53:16 CET 2009 $$$$ Bean2 - asynch value available at Thu Feb 19 21:53:17 CET 2009 ^^^^^^^^ Servlet: retrieved value from bean2 - leveraged asynch processing
Resources
Download: webappwithsloworasynchprocessing.zip.
See earlier article on Postloading Resources to a Web Page using AJAX: https://technology.amis.nl/blog/1024/ajax-based-post-loading-of-resources-in-html-pages-for-reuse-of-resources-and-fast-user-feedback