Servlet 3 Support in Spring MVC 3.2.

One of the major changes in Spring MVC 3.2  are Servlet 3.0 and its asynchronous support. Initially it was planned to be  added in Spring 3.1, but was postponed and only in 3.2 release dream came true.  


In this article I’m not going to discuss Servlet 3 pros and cons, neither its specification
If you need more info about it use this article.
The goal is to see how easily Servlet 3.0 features can be implemented with Spring MVC.  

There are two ways to use Asynchronous requests  in Spring MVC framework:

 

  • Using controller method which returns Callable<String> where String is the view returned by the method.

 

  • Using DefferedResult<T> which holds the result of Asynchronous operation updated by another thread.

 

We will write example for each case:

 

In our first example we have standard HTML form which uploads file to the server.

 

 <form method="post" action="uploadFile.do" enctype="multipart/form-data">
            <input type="file" name="file"/>
            <input type="submit"/>
        </form>

 

 

In order to enable multipart support in Spring we will add required bean configuration:

 

<bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
     <!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize" value="10000000"/>
</bean>

 

 

Our controller looks like this:

 

@Controller
public class AsyncController {
	
@RequestMapping(value="/uploadFile.do", method=RequestMethod.POST)
	public  Callable<String> uploadFile( @RequestParam("file")final MultipartFile file) {
		return new Callable<String>() {
			@Override
			public String call() throws Exception {
				  if (!file.isEmpty()) {
					  byte[] bytes = file.getBytes();
					  FileOutputStream fop = null;
					  try{
					     fop = new FileOutputStream(FOLDER_PATH+ file.getOriginalFilename());
					     FileChannel out = fop.getChannel();
					     ByteBuffer buff = ByteBuffer.allocateDirect(bytes.length);
					     out.write(buff);
					     out.force(false);
				         buff.clear();
				         out.close();
					  } catch (Exception ex){
						  throw new Exception(ex);
					  }finally{
				          if (fop != null)
				    	  	fop.close();
					  }
						 
				  }
				return "redirect:success.html";
			}
		};
	}
}

 

 

So as you can see the controller returns Callable<String>, Spring MVC start asynchronous process and submits callable to TaskExecutor, inside call() method we store file on server using java.nio methods. As soon as the result returned it passes Servlet container and dispatches it to DispatcherServlet.

 

Easy!!!!

 

Let’s look on another case:

 

We have web server which returns Mojodo Corp. stock quotes in real time. This server  long polled by Web clients using AJAX.

Every 5 second we receive quote update as JMS message and we need to dispatch result to all currently connected HTTP clients.

 

Or controller will return DefferedResult<String>. This object will be saved in Set which contains all objects of DefferedResult class returned by other container threads, For example, if three clients are currently long polling server the set will contain three DefferedResult objects.

 

Let’s see how it works.

 

We will create method which stores DfferedResult objects returned by other thread, its Spring bean wrapping HashSet.

 

@Service(value="deferredResultContainer")
public class DeferredResultContainer {

	private final Set<DeferredResult<String>> deferredResults = Collections.synchronizedSet(new HashSet<DeferredResult<String>>() );

	public void put(DeferredResult<String> deferredResult){
		deferredResults.add(deferredResult);
	}
	
	public void updateAllResults(String value){	
		for (DeferredResult<String> deferredResult : deferredResults){
			deferredResult.setResult(value);
		}
	}

 

 

The bean below will receive value from third party services and notify all clients. That bean simply updates all  DefferedResult objects located in the Set

 

@Service
public class QuateUpdateScheduler {
	
	@Autowired
	DeferredResultContainer deferredResultContainer;

 @Scheduled(fixedRate = 5000)
	    public void process() {
	        deferredResultContainer.updateAllResults(getQuateValueFromJMS());
	    }
}

 

 

and then the only thing is left is Spring MVC controller:

 

@Controller
public class AsyncController {
		
	@Autowired
	DeferredResultContainer deferredResultContainer;
		
	@RequestMapping(value="/getQuote.do", method=RequestMethod.GET)
	@ResponseBody
	public DeferredResult<String> getQuote(){
		final DeferredResult<String> deferredResult= new DeferredResult<String>();
		
		deferredResultContainer.put(deferredResult);
        deferredResult.onTimeout(new Runnable() {
			
			@Override
			public void run() {
				deferredResultContainer.remove(deferredResult);
				
			}
		});
		
		deferredResult.onCompletion(new Runnable() {
			
			@Override
			public void run() {
				deferredResultContainer.remove(deferredResult);
				
			}
		});
		return deferredResult;
	}
}

 

So our controller is just putting the DefferedResult<String> into Synchronized Map wrapped by Spring bean just before method is returning it.

After all clients are notified with result by service, its DefferedResult objects are automatically removed from Map. This part of code insures that when object is updated or request times out it will be automatically removed from Map.

 

 deferredResult.onTimeout(new Runnable() {
			
			@Override
			public void run() {
				deferredResultContainer.remove(deferredResult);
			}
		});
		
		deferredResult.onCompletion(new Runnable() {			
			@Override
			public void run() {
				deferredResultContainer.remove(deferredResult);
			}
		});

 

PS: Using Apache Tomcat 7.0.34 I was not able to get full asynchronous Servlet 3.0 processing before changing Connector to be  Http11NioProtocol.

 

<Connector connectionTimeout="200000" maxThreads="105" port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443"/>

 

That’s weird, promise to use Jetty next time :)

 

Developer