社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
编写测试用例
@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void order() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/order").contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
同步处理代码
@RestController
@RequestMapping("/order")
@Slf4j
public class AsyncController {
@GetMapping
public String order() throws InterruptedException {
log.info("主线程开始");
Thread.sleep(1000);
log.info("主线程结束");
return "success";
}
}
运行结果如下:
代码如下:
@GetMapping
public Callable<String> order() throws InterruptedException {
log.info("主线程开始");
Callable<String> result = new Callable<String>() {
@Override
public String call() throws Exception {
log.info("副线程开始");
Thread.sleep(1000);
log.info("副线程返回");
return "success";
}
};
log.info("主线程结束");
return result;
}
测试结果如下:
主线程几乎在同时就返回了,并不等待副线程处理结束。
之所以要使用DeferredResult来处理异步请求,是因为Runnable的方式并不能完全处理所有的需求。
使用Runnable的方式进行异步处理的时候,副线程必须是由主线程调起的。
在实际的业务场景中,不能满足需求。
需求案例(以下单操作为例)
在上图所示的业务流程中,接收下单请求的应用和真正处理下单逻辑的应用并不在一个应用,甚至不在同一台服务器中。
线程1接收到http请求之后,会把请求放在消息队列中,应用2所在的服务器,监听到有下单的http请求之后,由应用2来处理下单的逻辑。
当下单的逻辑处理完成之后,会将结果放置在消息队列中,同时在应用1中有另外一个线程,线程2监听到消息队列中有下单完成的消息之后,根据下单的结果做出相应的http响应。
在整个的场景之中,线程1和线程2完全是隔离的状态,使用Runnable的方式就不能满足需求了。
编码设计
使用一个对象来模拟消息队列,当收到下单请求之后,延迟1秒之后,在消息队列中放置“订单完成”的消息。
线程1的处理:
线程2的处理
线程2作为一个监听器,监听到消息队列中有订单完成的消息时,把结果返回。
DeferredResult
在线程1和线程2之间传递DefrredResult对象,这样才可以实现在线程2中使用线程1中生成的DeferredResult将处理的结果进行返回。
编码(示例代码为了简化过程,没有使用线程池)
模拟消息队列对象
@Component
public class MockQueue {
/**
* 下单消息
*/
private String placeOrder;
/**
* 订单完成的消息
*/
private String completeOrder;
public String getPlaceOrder() {
return placeOrder;
}
public void setPlaceOrder(String placeOrder) {
new Thread(() -> {
log.info("接到下单请求, " + placeOrder);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
this.completeOrder = placeOrder;
log.info("下单请求处理完毕," + placeOrder);
}).start();
}
public String getCompleteOrder() {
return completeOrder;
}
public void setCompleteOrder(String completeOrder) {
this.completeOrder = completeOrder;
}
}
DeferredResultHolder对象
@Component
@Getter
@Setter
public class DeferredResultHolder {
private Map<String, DeferredResult<String>> map = new HashMap<String, DeferredResult<String>>();
}
监听消息队列
@Slf4j
@Component
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
/**
* 监听Spring容器
* @param contextRefreshedEvent
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
new Thread(()->{
while (true) {
if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())) {
String orderNumber = mockQueue.getCompleteOrder();
log.info("返回订单处理结果:"+orderNumber);
deferredResultHolder.getMap().get(orderNumber).setResult("place order success");
mockQueue.setCompleteOrder(null);
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
测试结果如下:
在继承了WebMvcConfigurerAdapter类的配置类中可以注册异步拦截器,如下所示
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!