SpringMVC之异步分析Callable,WebAsyncTask,DeferredResult

SpringMVC之异步分析Callable,WebAsyncTask,DeferredResult,第1张

文章目录
  • 1 SpringMVC异步
    • 1.1 引言
    • 1.2 Callable
      • 1.2.1 Callable实例
      • 1.2.2 异步不能回调问题
    • 1.3 WebAsyncTask
      • 1.3.1 使用例子及说明
    • 1.4 DeferredResult
      • 1.4.1 DeferredResult简介
      • 1.4.2 DeferredResult使用
      • 1.4.3 完整示例
      • 1.4.4 DeferredResult总结

1 SpringMVC异步 1.1 引言

spring mvc同步接口在请求处理过程中一直处于阻塞状态,而异步接口可以启用后台线程去处理耗时任务。简单来说适用场景:

  1. 高并发;
  2. 高IO耗时 *** 作。

Spring MVC3.2之后支持异步请求,能够在controller中返回一个Callable或者DeferredResult

WebAsyncTask是对Callable的封装,提供了一些事件回调的处理,本质上区别不大。
DeferredResult使用方式与Callable类似,重点在于跨线程之间的通信。
@Async也是替换Runable的一种方式,可以代替我们自己创建线程。而且适用的范围更广,并不局限于Controller层,而可以是任何层的方法上。

Servlet3.0提供了AsyncContext支持异步处理。Spring DeferredResultAsyncContext进行了优化,实现了更简单的异步的实现。
Callable是并发编程提供的支持有返回值的异步处理方式。
WebAsyncTaskCallable的基础上进行了包装,提供了更强大的功能,比如:处理超时回调、错误回调、完成回调等。
@Async提供了更优为Runnable的实现方式。

至于在实际的代码中,我们可能还需要借助其它的类配合实现以此来达到更好的效果。

1.2 Callable 1.2.1 Callable实例
@Controller
public class CallableController {
    @RequestMapping(path = "/async1", method = RequestMethod.GET)
    @ResponseBody
    public Callable<String> asyncRequest() {
        return () -> {
            final long currentThread = Thread.currentThread().getId();
            final Date requestProcessingStarted = new Date();
 
            Thread.sleep(6000L);
 
            final Date requestProcessingFinished = new Date();
 
            return String.format(
                    "request: [threadId: %s, started: %s - finished: %s]"
                    , currentThread, requestProcessingStarted, requestProcessingFinished);
        };
    }
}
1.2.2 异步不能回调问题

使用了异步但是执行异步的方法,原因是在方法上加了@Async注解,之所以加这个注解是因为报错:

There was an unexpected error (type=Internal Server Error, status=500).
Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding true to servlet and filter declarations in web.xml

异步测试时一直报这个错误,提示我在web.xml开启异步支持,但是我是SpringBoot项目,于是开始网上查找
错误:加@Async注解,会更加异步,不能获取异步结果
正确:根本原因是容器注册问题,在springboot启动类的注解@SpringBootApplication旁边添加了@ServletComponentScan,才导致上面的报错和不能回调,有三种解决方法:

  • 去掉注解@ServletComponentScan
  • 添加容器注册(springboot项目)
  @Bean
    public ServletRegistrationBean dispatcherServlet() {
        ServletRegistrationBean registration = new ServletRegistrationBean(
                new DispatcherServlet(), "/");
        registration.setAsyncSupported(true);
        return registration;
    }
    @Bean
    DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

在过滤器那里添加asyncSupported = true的支持

@WebFilter(urlPatterns="/*",asyncSupported = true)
  • 修改web.xml(传统xml项目)
    需要在 web.xml 文件中的 servlet 定义中添加:"true"

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	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>Archetype Created Web Applicationdisplay-name>
	<context-param>
		<param-name>contextConfigLocationparam-name>
		<param-value>classpath:spring-mybatis.xmlparam-value>
	context-param>
	<context-param>
		<param-name>spring.profiles.activeparam-name>
		<param-value>devparam-value>
	context-param>
	<context-param>
		<param-name>spring.profiles.defaultparam-name>
		<param-value>devparam-value>
	context-param>
	<filter>
		<filter-name>encodingFilterfilter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
		<async-supported>trueasync-supported>
		<init-param>
			<param-name>encodingparam-name>
			<param-value>UTF-8param-value>
		init-param>
		<init-param>
			<param-name>forceEncodingparam-name>
			<param-value>trueparam-value>
		init-param>
	filter>
	<filter-mapping>
		<filter-name>encodingFilterfilter-name>
		<url-pattern>/*url-pattern>
	filter-mapping>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
	listener>

	<servlet>
		<servlet-name>SpringMVCservlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
		<init-param>
			<param-name>contextConfigLocationparam-name>
			<param-value>classpath:spring-mvc.xmlparam-value>
		init-param>
		<load-on-startup>1load-on-startup>
		<async-supported>trueasync-supported>
	servlet>
	<servlet-mapping>
		<servlet-name>SpringMVCservlet-name>
		<url-pattern>/url-pattern>
	servlet-mapping>
web-app>
1.3 WebAsyncTask

Spring 提供了对 异步任务 API, 采用 WebAsyncTask 类即可实现 异步任务. 对异步任务设置相应的 回调处理, 如当 任务超时, 异常抛出 等. 异步任务通常非常实用, 比如: 当一笔订单支付完成之后, 开启异步任务查询订单的支付结果。
简单来说:WebAsyncTask类是Spring提供的一步任务处理类。
另外要知道的一点就是:WebAsyncTaskCallable的升级版

1.3.1 使用例子及说明

WebAsyncTask : 在构造时写入Callable主要业务逻辑
WebAsyncTask.onCompletion(Runnable) :在当前任务执行结束以后,无论是执行成功还是异常中止,onCompletion的回调最终都会被调用
WebAsyncTask.onError(Callable>) :当异步任务抛出异常的时候,onError()方法即会被调用
WebAsyncTask.onTimeout(Callable>) :当异步任务发生超时的时候,onTimeout()方法即会被调用

@RequestMapping("/async")
    @ResponseBody
    public WebAsyncTask<String> asyncTask(){
        // 1000 为超时设置
        WebAsyncTask<String> webAsyncTask = new WebAsyncTask<String>(1000,new Callable<String>(){
            @Override
            public String call() throws Exception {
                //业务逻辑处理
                Thread.sleep(5000);
                String message = "username:wangbinghua";
                return message;
            }
        });
        webAsyncTask.onCompletion(new Runnable() {
            @Override
            public void run() {
                System.out.println("调用完成");
            }
        });
        webAsyncTask.onTimeout(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("业务处理超时");
                return "Time Out";
            }
        });

        return webAsyncTask;
    }
1.4 DeferredResult 1.4.1 DeferredResult简介

DeferredResultCallable实现功能类型,都是异步返回,只不过Callable不能直接设置超时时间,还需要和FutureTask配合使用,DeferredResult可以直接超时时间
点击此处了解Callable相关使用

使用DeferredResult目的:

  • API接口需要在指定时间内将异步 *** 作的结果同步返回给前端时;
  • Controller处理耗时任务,并且需要耗时任务的返回结果时;
  • 当一个请求到达API接口,如果该API接口的return返回值是DeferredResult,在没有超时或者DeferredResult对象没有设置setResult时,接口不会返回,但是Servlet容器线程会结束,DeferredResult另起线程来进行结果处理(即这种 *** 作提升了服务短时间的吞吐能力),并setResult,如此以来这个请求不会占用服务连接池太久,如果超时或设置setResult,接口会立即返回

使用DeferredResult的流程:

  1. 浏览器发起异步请求
  2. 请求到达服务端被挂起
  3. 向浏览器进行响应,分为两种情况:
    调用DeferredResult.setResult(),请求被唤醒,返回结果
    超时,返回一个设定的结果
  4. 浏览得到响应,再次重复1,处理此次响应结果

给人一种异步处理业务,但是却同步返回的感觉,和前端Promise对象非常类似(点击了解JavaScript中异步回调之Promise使用)

1.4.2 DeferredResult使用

创建实例对象

DeferredResult<ResponseEntity<List<User>>> deferredResult 
= new DeferredResult<>(20000L, new ResponseEntity<>(HttpStatus.NOT_MODIFIED));

设置回调
DeferedResult 两个监听器(onCompletion & onTimeout
DeferedResult对象调用setResult之后,响应完毕客户端,则直接调用onCompletion对应的方法。
当业务处理相当耗时,则响应客户端超时,也会调用onCompletion对应的方法以及onTimeout方法。
此时,响应客户端的内容为deferedResult.setErrorResult的内容,否则500错误。
发生异常,调用onCompletion方法,此时,响应客户端的内容为deferedResult.setErrorResult的内容,否则500错误。

deferredResult.onTimeout(() -> {
    log.info("调用超时");
    //调用超时这个 超时结果会覆盖 构造时的超时结果
    deferredResult.setResult("调用超时");
});

deferredResult.onCompletion(() -> {
    log.info("调用完成");
});

设置结果

new Thread(() -> {
    try {        
    	TimeUnit.SECONDS.sleep(10);
    	deferredResult.setResult(new ResponseEntity<>(userService.listUser(), HttpStatus.OK));    
    } catch (InterruptedException e) {
    	e.printStackTrace();    
}}).start();
1.4.3 完整示例
@GetMapping("/deferredResultUser")
public DeferredResult<String> deferredResultListUser() { 
 DeferredResult<String> deferredResult
  = new DeferredResult<>(20000L, "失败");    
 deferredResult.onTimeout(() -> { 
        log.info("调用超时");  
        //调用超时这个 超时结果会覆盖 构造时的超时结果
    deferredResult.setResult("调用超时");  
 });    
 deferredResult.onCompletion(() -> {
         log.info("调用完成");   
});    
new Thread(() -> { 
       try { 
           TimeUnit.SECONDS.sleep(10);
           deferredResult.setResult("OK"));        
           } catch (InterruptedException e) {  
             e.printStackTrace();           
        }    
}).start();    
return deferredResult;

}

客户端请求映射到控制器方法返回值为DeferredResult时,会立即释放Tomcat线程并将请求挂起,直到调用setResult()方法或者超时,才会响应客户端请求

1.4.4 DeferredResult总结

控制器中定义方法返回值为DeferredResult,会立即释放Tomcat线程,使用业务线程处理业务
DeferredResultMethodReturnValueHandler处理返回结果,开启异步处理并设置DeferredResultHandler
业务执行完成后调用setResult()方法,紧接着回调DeferredResultHandlerhandleResult()
设置结果并调度请求
创建Callable对象并设置调用方法为call()
通过反射方式调用call()得到返回值
使用返回值处理器处理返回值

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/787919.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存