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:见下文。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值