官方文档中说DeferredResult和Callable都是为了异步生成返回值提供基本的支持。简单来说就是一个请求进来,如果你使用了DeferredResult或者Callable,在没有得到返回数据之前,DispatcherServlet和所有Filter就会退出Servlet容器线程,但响应保持打开状态,一旦返回数据有了,这个DispatcherServlet就会被再次调用并且处理,以异步产生的方式,向请求端返回值。
这么做的好处就是请求不会长时间占用服务连接池,提高服务器的吞吐量。
Callable
Callable的实现比较简单,call()方法的返回值就是服务端返回给请求端的数据。
package com.imooc.demo.web.async;
import com.imooc.demo.web.async.two.DeferredResultHolder;
import com.imooc.demo.web.async.two.MockQueue;
import org.apache.commons.lang.RandomStringUtils;
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;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.concurrent.Callable;
/**
* 异步处理rest服务
*/
@Controller
@RequestMapping("/async")
public class AsyncController {
Logger logger = LoggerFactory.getLogger(AsyncController.class);
/**
* 使用Runnable异步处理rest服务
* 弊端:
* 1. 比如消息中间件. 请求 与 响应的线程并不是同一个线程.
* 2. 可以使用deferredResult对象来解决
* @return
*/
@RequestMapping("")
public Callable<String> asyncHandlerMethod() {
logger.info("主线程执行");
Callable<String> result = new Callable<String>() {
@Override
public String call() throws Exception {
logger.info("副线程执行");
Thread.sleep(1000);
logger.info("副线程返回");
return null;
}
};
logger.info("主线程返回");
return result;
/**
* 如下 , 两条线程
*/
/**
2018-07-24 16:11:18.883 INFO 2924 --- [nio-8080-exec-2] c.imooc.demo.web.async.AsyncController : 主线程执行
2018-07-24 16:11:18.884 INFO 2924 --- [nio-8080-exec-2] c.imooc.demo.web.async.AsyncController : 主线程返回
2018-07-24 16:11:18.890 INFO 2924 --- [ MvcAsync1] c.imooc.demo.web.async.AsyncController : 副线程执行
2018-07-24 16:11:19.891 INFO 2924 --- [ MvcAsync1] c.imooc.demo.web.async.AsyncController : 副线程返回
*/
}
}
DeferredResult
一旦启用了异步请求处理功能 ,控制器就可以将返回值包装在DeferredResult,控制器可以从不同的线程异步产生返回值。优点就是可以实现两个完全不相干的线程间的通信。
# 业务逻辑
1. 此处的业务 , 是模拟如下业务流程
1. 用户发起http请求
2. 应用1
3. 发消息给消息队列
4. 应用2监听并处理消息
5. 应用2返回处理结果并写入消息队列
6. 应用1监听消息队列的处理结果
7. 应用1返回http响应
8. 完毕
2. 业务通过 DeferredResult对象来实现异步调用.
3. 代码执行顺序
1. http://localhost:8080/async/order -> AsyncController (下单,并将其加入deferredResult)
2. QueueListener监听器监听spring初始化完毕事件. 监听MockQueue对象的下单标识.
3. MockQueue -> 标识消息中间件
如图:
- 模拟消息中间件的类
package com.imooc.demo.web.async.two;
import com.imooc.demo.web.async.AsyncController;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class MockQueue {
Logger logger = LoggerFactory.getLogger(MockQueue.class);
private String placeOrder; //下单
private String completeOrder; //完成
public String getPlaceOrder() {
return placeOrder;
}
public void setPlaceOrder(String placeOrder) throws InterruptedException {
// 此处按照逻辑 , 也应该是单独一个线程来处理实现的
new Thread(() -> {
logger.info("接到下单请求, {}",placeOrder);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//this.placeOrder = placeOrder;
this.completeOrder = placeOrder;
logger.info("下单请求完毕, {}",placeOrder);
}).start();
}
public String getCompleteOrder() {
return completeOrder;
}
public void setCompleteOrder(String completeOrder) {
this.completeOrder = completeOrder;
}
}
- 应用1操作类
package com.imooc.demo.web.async;
import com.imooc.demo.web.async.two.DeferredResultHolder;
import com.imooc.demo.web.async.two.MockQueue;
import org.apache.commons.lang.RandomStringUtils;
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;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.concurrent.Callable;
/**
* 异步处理rest服务
*/
@Controller
@RequestMapping("/async")
public class AsyncController {
Logger logger = LoggerFactory.getLogger(AsyncController.class);
/**
* 第二种方式: -> 下单
*/
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
@RequestMapping("/order")
@ResponseBody
public DeferredResult<String> order() throws InterruptedException {
logger.info("主线程执行");
// 生成订单号
String orderNumber = RandomStringUtils.randomNumeric(8);
// 返回result
mockQueue.setPlaceOrder(orderNumber);
DeferredResult<String> result = new DeferredResult<>();
deferredResultHolder.getMap().put(orderNumber,result);
logger.info("主线程返回");
return result;
// 然后编写监听器.
}
}
- spring监听器实现类模拟应用1监听
package com.imooc.demo.web.async.two;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
/**
* spring监听器 -> 泛型类型(ContextRefreshedEvent) -> spring初始化完毕事件
*/
@Component
public class QueueListener implements ApplicationListener<ContextRefreshedEvent>{
Logger logger = LoggerFactory.getLogger(QueueListener.class);
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
/**
* 需要新开一个线程 , 不然主线程会阻塞掉 !
*/
new Thread(() -> {
while (true) {
if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())){
String orderNumber = mockQueue.getCompleteOrder();
logger.info("返回订单处理结果: "+orderNumber);
deferredResultHolder.getMap().get(orderNumber).setResult("place order success");
mockQueue.setCompleteOrder(null);
logger.info("下单请求处理完毕," + orderNumber);
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}