Spring Async And Future – Report Generation Example
Spring 3.0 introduced the @Async annotation for executing tasks asynchronously. The asynchronous capabilities are highly useful in situations where we need to execute a long running task before allowing user input. For example, every time we open a Gmail message with attachment, Gmail performs a anti-virus scan before showing the download link to the user.
In this blog post, I will kick off a long running report asynchronously. Then I will use JQuery to periodically check if the report has been created. When the report is finally available, a link to the report is shown to the user.
We start by creating a ReportService as shown below. The generateReport method returns java.util.concurrent.Future interface. This interface allows the caller to retrieve the execution result at later time.
package com.inflinx.blog.springfuture.service; import java.util.concurrent.Future; import com.inflinx.blog.springfuture.domain.Report; public interface ReportService { public FuturegenerateReport(); }
The report service implementation is shown below. We simulate the “long running process” by calling the sleep method on the current thread. The method returns AsyncResult instance with the newly created Report as pass through value.
package com.inflinx.blog.springfuture.service; import java.util.concurrent.Future; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; import com.inflinx.blog.springfuture.domain.Report; @Service("reportsService") public class ReportServiceImpl implements ReportService { @Async public FuturegenerateReport() { try { Thread.sleep(15000); } catch (InterruptedException e) { e.printStackTrace(); } Report report = new Report(); report.setName("New Report"); report.setDescription("New Report Description"); return new AsyncResult (report); } }
We complete the service layer implementation by adding the following Spring configuration file:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <context:annotation-config /> <context:component-scan base-package="com.inflinx.blog.springfuture.service" /> <task:annotation-driven /> </beans>
Now that we have completed our service layer, let’s create an MVC Controller that uses the ReportService to create a new report. The new report is then saved in the user’s session.
package com.inflinx.blog.springfuture.web.controller; import java.util.concurrent.Future; import javax.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.inflinx.blog.springfuture.domain.Report; import com.inflinx.blog.springfuture.service.ReportService; @Controller public class HomeController { @Autowired @Qualifier("reportsService") private ReportService reportService; @RequestMapping(value="/home.html", method=RequestMethod.GET) public String showHome(HttpSession session) { Futurereport = reportService.generateReport(); session.setAttribute("report", report); return "home"; } }
To this controller, we then add a method that returns the status of the report generation. The method implementation simply retrieves the Future object and invokes the isDone method to check the status of the running task.
@RequestMapping("/reportstatus.json") @ResponseBody public String reportStatus(HttpSession session) { Futurereport = (Future )session.getAttribute("report"); if(report.isDone()) { System.out.println("Report Generation Done"); return "COMPLETE"; } else { System.out.println("Still Working on Report"); return "WORKING"; } }
We then add a method to the controller that shows the generated report as shown below:
@RequestMapping(value="/report.html", method=RequestMethod.GET) public String showReport(HttpSession session, Model model) throws InterruptedException, ExecutionException { Futurereport = (Future )session.getAttribute("report"); Report r = report.get(); model.addAttribute("report", r); return "report"; }
The report.jsp view returned by the above method is shown below:
<html> <head> <title>Home</title> </head> <body> <h1> Generated Report </h1> Name: ${report.name} <br /> Description: ${report.description} </body> </html>
We finally tie everything in the home.jsp view that gets returned by the showHome method. In the home.jsp, we use JQuery’s setInterval method to call the reportstatus.json URL every two seconds. Once we get the “COMPLETE” response, we show the download link.
<html> <head> <title>Home</title> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function() { // Check The Status Every 2 Seconds var timer = setInterval(function() { $.ajax({ url: 'reportstatus.json', success: function(data) { if(data === 'COMPLETE') { $('#reportLink').html("<a target='_target' href='report.html'>Download Report</a>"); clearInterval(timer); } } }); }, 2000); }); </script> </head> <body> <h1> Report Generator </h1> <div id="reportLink">Please Wait While Your Report Is Being Generated</div> </body> </html>
Here is a screenshot of the application’s home.jsp page:
I’ve been banging my head on the last hours to put @Async to work, and finally your example fitted my approach 🙂 Thank you for this!
That’s great example! Thank you
That´s a great example using @Async annottation and ajax. But how do i implement if i want to return several results for example to show the progress?
your blog post helped me – thanks much!
Excellent example, exactly what I was looking for.
However, I could not save the Future into the session as it does not implement Serializable. Maybe there is an implementation which does and you just have to change the configured Task Executor, but I did not find one. So I decided to simply save the Future in a Singleton Bean of type Map<String, Future>.
Also don’t forget to enable asynchronous execution (see http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html ), otherwise the @Async annotation will be ignored and you’ll end up with regular synchroneous processing.
@Eduardo
Thanks for sharing! 🙂
One way to get the progress of a task would be to use a sessionAttribute
You would declare it before you call generateReport in the homeContoller
session.setAttribute(“progressSession”, “started”);
Then pass the httpSession to generateReport method
You could then set the sessionAttribute to any value you like in generateReport
session.setAttribute(“progressSession”, “step 1”);
I then get the progressSession string in the reportStatus method so we can show it in the jsp
String progressSession = (String) session.getAttribute(“progressSession”);
//return “WORKING”;
return progressSession;
Hope that helps.
good example – thanks
Great example reduce my R & D time on async for my company Thanks.
simple and funky example.
Thx a lot.