Asynchronous request processing in Spring MVC

In an age of APIs, AJAX clients, and devices, web servers are under exponentially growing traffic. Figuring out ways to make servers more scalable is an ongoing challenge for server vendors. The traditional one thread per HTTP connection strategy does not scale well for a bigger number of concurrent user access. In this model, every request blocks a thread from the thread pool allocated by the Servlet container until the request is completely processed (the examples shown so far follow this model). When AJAX clients—where a single screen frequently fires multiple concurrent connection requests—join the traditional, blocking I/O model of web servers with long-running processes, servers easily get exhausted due to the thread starvation problem, since no free thread is available in the pool. This makes the application unavailable on increased load.

Asynchronous HTTP request processing is a technique that utilizes the non-blocking I/O capability of the Java platform's NIO API. In this model, a server thread is not constantly attached to a persistent HTTP connection during the whole request processing. The Servlet container releases the container thread as soon as the request is received and further processing is delegated to a thread managed by another application (Spring, in this case) so that the container thread is free to serve new incoming requests. This non-blocking request processing model saves a lot of server resources and steadily increases the scalability of the server.

Servlet 3.0 introduced asynchronous processing support, and Spring has implemented this support starting from Spring 3.2. As of 4.2, Spring provides two easy ways of defining asynchronous request handlers:

  • Returning a java.util.concurrent.Callable instance instead of a value and producing the actual return value form inside the call method of Callable, that is, a thread managed by Spring, instead of a Servlet container
  • Returning an instance of the Spring-specific DeferredResult type and producing the actual return value form inside any other thread or external event, such as JMS or a Quartz scheduler

Both these methods release the container thread at the earliest possible opportunity and use external threads to continue long-running transactions asynchronously. Let's look at an example for the first option, that is, using Callable:

@RequestMapping(path="/tasks/new.xml",method= RequestMethod.POST, consumes = "application/xml", produces = "application/xml")
@ResponseBody
public Callable<CreateTaskResponse> createNewTaskXMLAsyncCallable( @RequestBody CreateTaskRequest createRequest) {
   return new Callable<CreateTaskResponse>() {

      @Override
      public CreateTaskResponse call() throws Exception {
         Task task = new Task();
         task.setName(createRequest.getTaskName());
         . . .
         Task persistedTask = taskService.createTask(task);
         // Send an email here...
         // Send some push notifications here...
         . . .
         return new CreateTaskResponse(persistedTask);
      }
   };
}

In this method, you can see that the handler method returns the Callable object immediately after receiving the request and without waiting for the Callable.call() method to execute. Spring MVC invokes the call() method in another thread using TaskExecutor, and the response is dispatched back to the Servlet container once the call() method returns the value.

The following is an example of how to use DeferredResult:

@RequestMapping(path = "/tasks/new-async-deferred.json", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
@ResponseBody
public DeferredResult<CreateTaskResponse> createNewTaskJSONAsyncDeferredResult( @RequestBody CreateTaskRequest createRequest) {

   DeferredResult<CreateTaskResponse> defResult = new DeferredResult<>();
   CompletableFuture.runAsync(new Runnable() {
      @Override
      public void run() {
         Task task = new Task();
         task.setName(createRequest.getTaskName());
         . . .
         Task persistedTask = taskService.createTask(task);
         // Send an email here...
         // Send some push notifications here...
         defResult.setResult(newCreateTaskResponse(persistedTask));
      }
   });
   return deferredResult;
}

Remember, you must enable asynchronous processing support in DispatcherServlet as well as for all Servlet filters declared in the web.xml file (or wherever you are defining them—maybe in the JavaConfig class) for it to work. The following code shows how you set it in web.xml:

<servlet>
   <servlet-name>appServlet</servlet-name>
   <servlet-class>
      org.springframework.web.servlet.DispatcherServlet</servlet-class>
   . . .
   <async-supported>true</async-supported>
</servlet>

You may choose any of the preceding approaches as per your convenience to enable asynchronous processing. Consider designing all your non-trivial services to work asynchronously for high scalability and performance.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset