springboot系列16,Servlet(2)
Servlet异步操作
顾名思义:异步
关于异步操作其实很简单,ResponseBody返回值需要使用DeferredResult,具体可以看DeferredResult的源码,这里不贴了。
@ComponentScan(basePackages = "com.haozi.servlet.controller")
public class DefaultAnnotationConfigDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() { //对应web.xml中的init-param
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() { //DispatcherServlet
return new Class[]{this.getClass()};
}
@Override
protected String[] getServletMappings() { //映射
return new String[]{"/"};
}
}
/**
* {@link HelloWorldAsyncController} 异步controller
*/
@RestController
public class HelloWorldAsyncController {
@GetMapping("/haozi")
public DeferredResult<String> helloworld() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
deferredResult.setResult("haozi");
deferredResult.onCompletion(() -> {
System.out.println("HelloWorldAsyncController:执行结束");
});
return deferredResult;
}
}
运行正常,控制台也正常打印
通过debug可以知道,这里的执行结束是mapping执行后回调的。
但这样并不能看出是异步,加个线程信息:
/**
* {@link HelloWorldAsyncController} 异步controller
*/
@RestController
public class HelloWorldAsyncController {
@GetMapping("/haozi")
public DeferredResult<String> helloworld() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
deferredResult.setResult("haozi");
this.println("耗子肉");
deferredResult.onCompletion(() -> {
this.println("执行结束");
});
return deferredResult;
}
private static void println(Object object){
String threadName = Thread.currentThread().getName();
System.out.println("HelloWorldAsyncController[" + threadName + "]:" + object);
}
}
但执行回调会发现是同一个线程
也就是说这里是同步的。
怎样解决这个问题?加超时时间
/**
* {@link HelloWorldAsyncController} 异步controller
*/
@RestController
public class HelloWorldAsyncController {
@GetMapping("/haozi")
public DeferredResult<String> helloworld() {
//这里不设置超时时间的话,超时时间是取得tomcat容器的超时时间
DeferredResult<String> deferredResult = new DeferredResult<String>(50L);
// deferredResult.setResult("haozi");
this.println("耗子肉");
deferredResult.onCompletion(() -> {
this.println("执行结束");
});
deferredResult.onTimeout(() ->{
this.println("执行超时");
});
return deferredResult;
}
private static void println(Object object){
String threadName = Thread.currentThread().getName();
System.out.println("HelloWorldAsyncController[" + threadName + "]:" + object);
}
}
我们通过不设置返回值,让程序超时,来测试,运行结果:
这样可以发现,线程变了,也就是发生了异步操作。
通过文档可以看出,DeferredResult异步需要**,那**在哪呢?
在我们进行DispatcherServlet初始化配置类DefaultAnnotationConfigDispatcherServletInitializer中,有一个父类AbstractAnnotationConfigDispatcherServletInitializer,他的父类AbstractDispatcherServletInitializer的构造器中有一个关于异步的设置
所以这里默认就是**的,所以当子类没有覆盖这个地方的时候,都是默认**的。
接下来模拟真实情况,运行时间不定:
/**
* {@link HelloWorldAsyncController} 异步controller
*/
@RestController
@EnableScheduling
public class HelloWorldAsyncController {
private final BlockingQueue<DeferredResult<String>> queue = new ArrayBlockingQueue<>(5);
//超时随机数
private final Random random = new Random();
@Scheduled(fixedRate = 5000)
public void process() throws InterruptedException { //定时操作
DeferredResult<String> result = null;
do {
result = queue.take();
//随机超时时间
long timeout = this.random.nextInt(100);
//模拟等待时间
Thread.sleep(timeout);
//计算结果
result.setResult("haozi");
this.println("耗子肉执行计算结果,消耗" + timeout + "毫秒。");
}while (result != null);
}
@GetMapping("/haozi")
public DeferredResult<String> helloworld() {
//这里不设置超时时间的话,超时时间是取得tomcat容器的超时时间
DeferredResult<String> deferredResult = new DeferredResult<String>(50L);
//入队操作
queue.offer(deferredResult);
this.println("执行开始");
deferredResult.onCompletion(() -> {
this.println("执行结束");
});
deferredResult.onTimeout(() ->{
this.println("执行超时");
});
return deferredResult;
}
private static void println(Object object){
String threadName = Thread.currentThread().getName();
System.out.println("HelloWorldAsyncController[" + threadName + "]:" + object);
}
}
这里稍微补充一下,@EnableScheduling是spring自带的定时任务功能,具体实现注解:@Scheduled,写法和参数自行百度。BlockingQueue为阻塞队列,https://blog.csdn.net/weixin_38481963/article/details/88372920
但这种方法有点太麻烦了,所以还有一种方法,返回值类型改为Callable
@GetMapping("/callable-haozi")
public Callable<String> callablehaozi() {
final long start = System.currentTimeMillis();
this.println("耗子肉");
return () -> {
final long cost = System.currentTimeMillis() - start;
this.println("耗子肉执行计算结果,消耗" + cost + "毫秒。");
return "haozirou";
};
}
运行结果:
但 Callable没有回调函数,具体可以用ListenableFuture,具体看https://blog.csdn.net/liyantianmin/article/details/70911224
还有一种方式,使用CompletionStage
@GetMapping("/completion-haozi")
public CompletionStage<String> completionhaozi() {
final long start = System.currentTimeMillis();
this.println("耗子肉");
return CompletableFuture.supplyAsync(()->{
final long cost = System.currentTimeMillis() - start;
this.println("耗子肉执行计算结果,消耗" + cost + "毫秒。");
//异步执行结果
return "haozi";
});
}
运行结果:
Spring MVC异步Servlet实现原理
HandleMethodReturnValueHandler:这个在之前已经见到了,用于处理返回值的,其实在mvc体系中,mapping的返回值不是实体类或String等常用格式,那肯定就是有servlet内部或自定义的处理。
DispatcherServlet整合:前面已经说过了,在自定义配置类的父类的父类中,有默认开启异步的地方,而那个设置的对象是属于servlet的。
Servlet 3.0 AsyncContext:见下文。
上一篇: 在kali安装pwngdb
推荐阅读
-
NVIDIA发布六款Quadro系列专业卡:16GB HBM2显存
-
Spring Boot2 系列教程 (二) | 第一个 SpringBoot 工程详解
-
springboot深入浅出系列(16章97节)-看了都说好
-
SpringBoot起飞系列-配置嵌入式Servlet容器(八)
-
手写tomcat系列2-第一个Servlet容器
-
springboot系列16,Servlet(2)
-
SpringBoot2.x系列教程之配置大全03
-
SpringBoot2.x系列二:整合第三方组件Mybatis、JPA、Redis、Elasticsearch、ActiveMQ、Kafka、Logback
-
SpringBoot2.x系列教程65--SpringBoot整合RabbitMQ使用教程
-
SpringBoot2.x系列教程59--SpringBoot整合消息队列之JMS简介