SpringMVC 处理异步请求 - TFdream/blog GitHub Wiki

springmvc 3.2开始就支持servlet3.0的异步请求。平常我们请求一个controller一般都是同步的,如果在代码执行中,遇到耗时的业务操作,那servlet容器线程就会被锁死,当有其他请求进来的时候就会受堵了,有助于提高系统的吞吐量。

springmvc3.2之后支持异步请求,能够在controller中返回一个Callable或者DeferredResult。当返回Callable的时候,大概的执行过程如下:

  • 当controller返回值是Callable的时候,springmvc就会启动一个线程将Callable交给TaskExecutor去处理
  • 然后DispatcherServlet还有所有的spring拦截器都退出主线程,然后把response保持打开的状态
  • 当Callable执行结束之后,springmvc就会重新启动分配一个request请求,然后DispatcherServlet就重新调用和处理Callable异步执行的返回结果,然后返回视图

DeferredResult的执行过程和Callable差不多,唯一不同的时候,DeferredResult是由应用程序其他线程执行返回结果,而Callable是由TaskExecutor执行返回结果。

实战

1. 依赖

要使用Spring MVC的异步功能,你得先确保你用的是Servlet 3.0或以上的版本,pom.mxl配置:

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.3.6.RELEASE</version>
    </dependency>

2. web.xml

需要在web.xml加上servlet3.x的scheme库,如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app
        xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
        version="3.1">
  <display-name>springmvc async Demo</display-name>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:/applicationContext*.xml;</param-value>
  </context-param>

  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath*:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

3. 使用DefferedResult

package springmvc.tutorials.async.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Ricky Fung
 */
@Controller
@RequestMapping("/demo")
public class DemoController {
    private final AtomicInteger counter = new AtomicInteger(1);
    //业务处理线程池
    private final ExecutorService pool = Executors.newFixedThreadPool(5);

    @RequestMapping("/deferredResult")
    @ResponseBody
    public DeferredResult<Object> request(@RequestParam String username) {

        int seq = counter.getAndIncrement();
        System.out.println("请求seq:"+seq+"开始 in thread:"+Thread.currentThread().getName());
        DeferredResult<Object> deferredResult = new DeferredResult<>(5000L);
        //异步处理
        dealInOtherThread(deferredResult, seq);
        return deferredResult;
    }

    private void dealInOtherThread(final DeferredResult<Object> deferredResult, final int seq) {

        pool.execute(new Runnable() {
            @Override
            public void run() {

                System.out.println("请求seq:"+seq+"处理开始 in thread:"+Thread.currentThread().getName());
                sleep(2000);
                System.out.println("请求seq:"+seq+"处理结束 in thread:"+Thread.currentThread().getName());

                Map<String, String> map = new HashMap<>();
                map.put("seq", String.valueOf(seq));
                map.put("thread", Thread.currentThread().getName());
                deferredResult.setResult(map);
            }
        });
    }

    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

4. 利用Callable执行异步请求

package springmvc.tutorials.async.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.WebAsyncTask;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Ricky Fung
 */
@Controller
@RequestMapping("/foo")
public class FooController {
    private final AtomicInteger counter = new AtomicInteger(1);

    @RequestMapping("/callable")
    @ResponseBody
    public Callable<Object> request() {

        final int seq = counter.getAndIncrement();
        System.out.println("请求seq:"+seq+"开始 in thread:"+Thread.currentThread().getName());

        Callable<Object> callable = new Callable<Object>() {
            @Override
            public Object call() throws Exception {

                System.out.println("请求seq:"+seq+"处理开始 in thread:"+Thread.currentThread().getName());
                sleep(2000);
                System.out.println("请求seq:"+seq+"处理结束 in thread:"+Thread.currentThread().getName());

                Map<String, String> map = new HashMap<>();
                map.put("seq", String.valueOf(seq));
                map.put("thread", Thread.currentThread().getName());
                return map;
            }
        };
        return callable;
    }

    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

5. 使用WebAsyncTask包装Callable

使用WebAsyncTask包装Callable,可以自定义客户端超时间,如下:

package springmvc.tutorials.async.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.WebAsyncTask;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Ricky Fung
 */
@Controller
@RequestMapping("/foo")
public class FooController {
    private final AtomicInteger counter = new AtomicInteger(1);

    @RequestMapping("/webAsyncTask")
    @ResponseBody
    public WebAsyncTask<List<String>> req() {

        final int seq = counter.getAndIncrement();
        System.out.println("请求seq:"+seq+"开始 in thread:"+Thread.currentThread().getName());

        Callable<List<String>> callable = new Callable<List<String>>() {
            @Override
            public List<String> call() throws Exception {

                System.out.println("请求seq:"+seq+"处理开始 in thread:"+Thread.currentThread().getName());
                sleep(2000);
                System.out.println("请求seq:"+seq+"处理结束 in thread:"+Thread.currentThread().getName());

                List<String> list = new ArrayList<>();
                list.add(String.valueOf(seq));
                list.add(Thread.currentThread().getName());
                return list;
            }
        };

        return new WebAsyncTask<>(10000, callable);
    }

    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

⚠️ **GitHub.com Fallback** ⚠️