IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringMVC之异步分析CallableWebAsyncTaskDeferredResult -> 正文阅读

[Java知识库]SpringMVC之异步分析CallableWebAsyncTaskDeferredResult

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 <async-supported>true</async-supported> 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 定义中添加:"<async-supported>true</async-supported>"
<?xml version="1.0" encoding="UTF-8"?>
<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 Application</display-name>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring-mybatis.xml</param-value>
	</context-param>
	<context-param>
		<param-name>spring.profiles.active</param-name>
		<param-value>dev</param-value>
	</context-param>
	<context-param>
		<param-name>spring.profiles.default</param-name>
		<param-value>dev</param-value>
	</context-param>
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<async-supported>true</async-supported>
		<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>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

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

	<servlet>
		<servlet-name>SpringMVC</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>
		<async-supported>true</async-supported>
	</servlet>
	<servlet-mapping>
		<servlet-name>SpringMVC</servlet-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 "<h1>Time Out</h1>";
            }
        });

        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()得到返回值
使用返回值处理器处理返回值

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-30 08:33:07  更:2022-04-30 08:37:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:55:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码