欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

springboot系列16,Servlet(2)

程序员文章站 2022-07-13 13:38:58
...

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;
    }
}

运行正常,控制台也正常打印

springboot系列16,Servlet(2)

通过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);
    }
}

但执行回调会发现是同一个线程

springboot系列16,Servlet(2)

也就是说这里是同步的。

怎样解决这个问题?加超时时间

/**
 * {@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);
    }
}

我们通过不设置返回值,让程序超时,来测试,运行结果:

springboot系列16,Servlet(2)

这样可以发现,线程变了,也就是发生了异步操作。

通过文档可以看出,DeferredResult异步需要**,那**在哪呢?

在我们进行DispatcherServlet初始化配置类DefaultAnnotationConfigDispatcherServletInitializer中,有一个父类AbstractAnnotationConfigDispatcherServletInitializer,他的父类AbstractDispatcherServletInitializer的构造器中有一个关于异步的设置

springboot系列16,Servlet(2)

springboot系列16,Servlet(2)

 所以这里默认就是**的,所以当子类没有覆盖这个地方的时候,都是默认**的。

接下来模拟真实情况,运行时间不定:

/**
 * {@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

springboot系列16,Servlet(2)

springboot系列16,Servlet(2)

 但这种方法有点太麻烦了,所以还有一种方法,返回值类型改为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";
        };
    }

运行结果:

springboot系列16,Servlet(2)

但 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";
        });
    }

运行结果: 

springboot系列16,Servlet(2)


Spring MVC异步Servlet实现原理

HandleMethodReturnValueHandler:这个在之前已经见到了,用于处理返回值的,其实在mvc体系中,mapping的返回值不是实体类或String等常用格式,那肯定就是有servlet内部或自定义的处理。

DispatcherServlet整合:前面已经说过了,在自定义配置类的父类的父类中,有默认开启异步的地方,而那个设置的对象是属于servlet的。

Servlet 3.0 AsyncContext:见下文。

 

 

 

相关标签: 学习 springboot