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: