Home > Spring > Spring Async And Future – Report Generation Example

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 Future generateReport();
	
}

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 Future generateReport() {
		
		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) 
	{
		Future report = 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) {
		Future report = (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 
	{
		Future report = (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:

Here is a screenshot after the report has been generated:

The screenshot of the newly generated report:

Categories: Spring Tags: , ,
  1. Jorge Santos
    October 31st, 2012 at 17:01 | #1

    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!

  2. Pavel
    January 30th, 2013 at 12:57 | #2

    That’s great example! Thank you

  3. Eduardo
    March 1st, 2013 at 16:11 | #3

    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?

  4. Satish
    March 14th, 2013 at 13:29 | #4

    your blog post helped me – thanks much!

  5. Alexander Pohl
    May 8th, 2013 at 01:40 | #5

    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.

  6. Seamus
    September 18th, 2013 at 06:35 | #6

    @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.

  7. Vladimir S
    October 11th, 2013 at 10:17 | #7

    good example – thanks

  8. Tushar Patel
    December 26th, 2013 at 00:16 | #8

    Great example reduce my R & D time on async for my company Thanks.

  9. salim239
    June 10th, 2014 at 06:30 | #9

    simple and funky example.
    Thx a lot.

  1. December 30th, 2013 at 08:09 | #1