- 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总结
spring mvc
同步接口在请求处理过程中一直处于阻塞状态,而异步接口可以启用后台线程去处理耗时任务。简单来说适用场景:
- 高并发;
- 高IO耗时 *** 作。
Spring MVC3.2
之后支持异步请求,能够在controller
中返回一个Callable
或者DeferredResult
WebAsyncTask
是对Callable
的封装,提供了一些事件回调的处理,本质上区别不大。
DeferredResult
使用方式与Callable
类似,重点在于跨线程之间的通信。
@Async
也是替换Runable
的一种方式,可以代替我们自己创建线程。而且适用的范围更广,并不局限于Controller
层,而可以是任何层的方法上。
Servlet3.0
提供了AsyncContext
支持异步处理。Spring DeferredResult
在AsyncContext
进行了优化,实现了更简单的异步的实现。
Callable
是并发编程提供的支持有返回值的异步处理方式。
WebAsyncTask
在Callable
的基础上进行了包装,提供了更强大的功能,比如:处理超时回调、错误回调、完成回调等。
@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 addingtrue
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
提供的一步任务处理类。
另外要知道的一点就是:WebAsyncTask
是Callable
的升级版
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简介
DeferredResult
和Callable
实现功能类型,都是异步返回,只不过Callable
不能直接设置超时时间,还需要和FutureTask
配合使用,DeferredResult
可以直接超时时间
点击此处了解Callable相关使用
使用DeferredResult
目的:
API
接口需要在指定时间内将异步 *** 作的结果同步返回给前端时;Controller
处理耗时任务,并且需要耗时任务的返回结果时;- 当一个请求到达
API
接口,如果该API
接口的return
返回值是DeferredResult
,在没有超时或者DeferredResult
对象没有设置setResult
时,接口不会返回,但是Servlet
容器线程会结束,DeferredResult
另起线程来进行结果处理(即这种 *** 作提升了服务短时间的吞吐能力),并setResult
,如此以来这个请求不会占用服务连接池太久,如果超时或设置setResult
,接口会立即返回
使用DeferredResult
的流程:
- 浏览器发起异步请求
- 请求到达服务端被挂起
- 向浏览器进行响应,分为两种情况:
调用DeferredResult.setResult()
,请求被唤醒,返回结果
超时,返回一个设定的结果 - 浏览得到响应,再次重复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()
方法或者超时,才会响应客户端请求
控制器中定义方法返回值为DeferredResult
,会立即释放Tomcat
线程,使用业务线程处理业务
由DeferredResultMethodReturnValueHandler
处理返回结果,开启异步处理并设置DeferredResultHandler
业务执行完成后调用setResult()
方法,紧接着回调DeferredResultHandler
的handleResult()
设置结果并调度请求
创建Callable
对象并设置调用方法为call()
通过反射方式调用call()
得到返回值
使用返回值处理器处理返回值
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)