Tuesday, September 15, 2015

Asynchronous Spring MVC – Hello World Example

Asynchronous processing is supported since Servlet 3.0. Spring MVC makes asynchronous processing even simpler. Here is a hello world level demo on how to do that using Spring MVC.

Basically there are 2 things need to do:

  1. configure web.xml to turn on async support
  2. return Callable instance  instead of what you mostly return, String/Model/ModelAndView  etc, from a controller’s method.

Let’s suppose you know how to setup a maven managed Spring MVC project in Eclipse. If you don’t , here is the tutorial

0. What you need

JDK 1.7+

Maven 3.2.1

1. Maven  pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shengwang.demo</groupId>
<artifactId>spring-mvc-asynchronous-basic</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>spring-mvc-asynchronous-basic Maven Webapp</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-framework.version>4.1.6.RELEASE</spring-framework.version>

<!-- Logging -->
<logback.version>1.1.2</logback.version>
<slf4j.version>1.7.7</slf4j.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-framework.version}</version>
</dependency>

<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>

<build>
<finalName>spring-mvc-asynchronous-basic</finalName>
<!-- Use Java 1.7 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

In maven pom.xml, dependencies like Spring MVC and logging has been added. Also JDK version is set to 1.7


2. configure web.xml to support async


Use <async-supported>true</async-supported> to turn on async support for servlets and filters. Use <dispatcher>ASYNC</dispatcher> for filter-mapping  if any.

<web-app xmlns="http://java.sun.com/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Demo for mvc hello world config</display-name>


<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<!-- turn on async support for servlet -->
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>

3. Define java class


First is the service class, just sleep 3 seconds to mimic a slow task.

package com.shengwang.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class HelloService {
Logger logger = LoggerFactory.getLogger(HelloService.class);

public String doSlowWork() {
logger.info("Start slow work");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("finish slow work");
// return "forward:/another"; // forward to another url
return "index"; // return view's name
}
}

The hello service does nothing except sleep a few seconds then return view's name.


The controller look like below.

package com.shengwang.demo;

import java.util.concurrent.Callable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;


@Controller
public class HelloController {
Logger logger = LoggerFactory.getLogger(HelloController.class);

@Autowired
HelloService helloService;

@RequestMapping("/helloAsync")
public Callable<String> sayHelloAsync() {
logger.info("Entering controller");

Callable<String> asyncTask = new Callable<String>() {

@Override
public String call() throws Exception {
return helloService.doSlowWork();
}
};

logger.info("Leaving controller");
return asyncTask;
}
}

The method sayHelloAsync return a Callable instance.


4. Configure Spring MVC


Compare to the basic hello world Spring mvc configuration in tutorial on “How to setup Spring MVC project with maven in Eclipse”, the difference is the task executor setup for async tasks.

<?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:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<context:component-scan base-package="com.shengwang.demo" />

<!-- ================================== -->
<!-- 0. Set up task executor for async -->
<!-- ================================== -->
<mvc:annotation-driven>
<mvc:async-support default-timeout="30000" task-executor="taskExecutor"/>
</mvc:annotation-driven>
<!-- modify the parameters of thread pool -->
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5"/>
<property name="maxPoolSize" value="50"/>
<property name="queueCapacity" value="10"/>
<property name="keepAliveSeconds" value="120"/>
</bean>

<!-- ================================== -->
<!-- 1. mapping static resources -->
<!-- ================================== -->
<mvc:resources location="/static-resources/css/" mapping="/css/**" cache-period="3600"/>
<mvc:resources location="/static-resources/img/" mapping="/img/**" cache-period="3600"/>
<mvc:resources location="/static-resources/js/" mapping="/js/**" cache-period="3600"/>


<!-- ================================== -->
<!-- 2. view resolver for JSP -->
<!-- ================================== -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

In real project, the task executor must be explicitly setup instead of using the default SimpleAsyncTaskExecutor, which can not be used in production. As Spring document says:



It is highly recommended to configure this property since by default Spring MVC uses SimpleAsyncTaskExecutor.


5. Run the async web app


Run the project and access http://localhost:8080/spring-mvc-asynchronous-basic/helloAsync, the log from console looks like below


image


The controller thread(http-bio-8080-exec-7) exits immediately, but another MVC mangaged thread(taskExecutor-1) takes 3 seconds to finish the slow work. Although the controller thread finishes very fast, the connection keeps open, browser doesn’t get response until 3 seconds later, all threads finish.

11 comments:

  1. Hi Thanks for this demo.... but after reaching the controller it's giving me directly the index page without performing the doslowwork() method part. Please help

    ReplyDelete
    Replies
    1. Hi there, I can't point out exactly but sounds like async doesn't work. First make sure you have correctly reach the right method in your controller, then maybe you should check if you have turn async support on in web.xml and make sure you do reply a callable for your controller method.

      Delete
    2. I haven't use maven in the application. the async support turns on. controller is working but async part is not. If possible could i share the application with you.

      Delete
  2. Hi,
    Very much appreciated of your detailed explanation.
    I would like to use spring mvc async support as described by you, but i would like to have event notification service from the controller response, which keeps sending out events. Can you please let me know how can this be accomplished, as the async support discussed above will return a single response asynchronously but it does not send multiple outputs in intervals.

    Thanks
    Naresh

    ReplyDelete
    Replies
    1. The controller can only response to the user(browser) ONCE. Async Servlet is designed to improve server's performance by reduce thread number in large concurrent environment. For your requirement, sending out multi-times from controller, use websocket or long-poll instead.

      Delete
  3. Hi Sheng, How we can download your source code (this project)?
    Thanks,
    Sayali

    ReplyDelete
  4. Sheng, Thanks a lot for your article. I followed your instruction. That's how I started getting our application to switch to Spring MVC async using Callable. The following articles are also useful.
    https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-async (Which give an overall picture and If you use java filter and Spring MVC interceptor), http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/AsyncHandlerInterceptor.html (Useful for MVC interceptors too).

    Our async application is deployed in Tomcat. That works fine. However, I just couldn't get one test passed. Our tests use embedded Jetty 8 server. I traced and found.out that Spring MVC will return 404 Not Found "No mapping request found for HTTP request URI....". if the async web request execution time is longer than 100ms, Very similar requests with execution time < 100ms works fine in embedded Jetty 8. Similar requests with async web request taking longer than 1 second (due to remote Mongodb) work fine in Tomcat. I configured default-timeout in mvc:async-support. It does not look like it has something to do with this issue.

    According to timeout section in the article
    https://spring.io/blog/2012/05/10/spring-mvc-3-2-preview-making-a-controller-method-asynchronous/,
    "You can configure the timeout value (of async web request) through the MVC Java config and the MVC namespace, or directly on the RequestMappingHandlerAdapter. If not configured, the timeout value will depend on the underlying Servlet container. On Tomcat it is 10 seconds and it starts after the initial Servlet container thread exits all the way out."

    That seems to match what I experienced. It did not elaborate how to do that. Let me know if you know any solution. Currently, I am research on
    spring.mvc.async.request-timeout (preferred) and also if I can override embedded Jetty server servlet (async) request timeout


    ReplyDelete
  5. Hi Sheng. Could you give me an example using this project with MDC Logger? I'm trying to use Spring MVC Rest Async but when I try to use MDC but I've lost the reference and the MDC logger doesn't work. Could you help me, please?
    Thanks!

    ReplyDelete
    Replies
    1. Of course, MDC is just for Request Context (In other word, ThreadLocal of Request)
      When you call MDC in other thread -> Difference from ThreadLocal.
      So, you must create other map to store you MDC and then pass it down the pipeline.
      Ex:
      Map ctx = MDC. getCopyOfContextMap();
      Callable x = new Callable(){
      public Object call(){
      String myId = ctx.get("your-key");
      log.("{}", myId);
      return ;
      }
      };

      Delete
  6. browser doesn’t get response until 3 seconds later, all threads finish. can browser get
    response when main thread finish,if some thread is time-consuming,the custormer will wait for a long time

    ReplyDelete
    Replies
    1. Spring sync mvc is not design for this actually, it is for optimization on server side. For browser<->server interaction, the big picture is still the same(i.e blocking).

      Delete

Powered by Blogger.

About The Author

My Photo

Has been a senior software developer, project manager for 10+ years. Dedicate himself to Alcatel-Lucent and China Telecom for delivering software solutions.

Pages

Unordered List