• Servlet Async 异步调用处理方式


    异步调用

    异步调用前,我们说说它对应的同步调用。通常开发过程中,一般上我们都是同步调用,即:程序按定义的顺序依次执行的过程,每一行代码执行过程必须等待上一行代码执行完毕后才执行。而异步调用指:程序在执行时,无需等待执行的返回值可继续执行后面的代码。显而易见,同步有依赖相关性,而异步没有,所以异步可并发执行,可提高执行效率,在相同的时间做更多的事情。

    回调:处理异步同步外,还有一个叫回调。其主要是解决异步方法执行结果的处理方法,比如在希望异步调用结束时返回执行结果,这个时候就可以考虑使用回调机制。

    原生异步请求API说明

    在编写实际代码之前,我们来了解下一些关于异步请求的api的调用说明。

    • 获取AsyncContext:根据HttpServletRequest对象获取。
    1
    AsyncContext asyncContext = request.startAsync();
    • 设置监听器:可设置其开始、完成、异常、超时等事件的回调处理

    其监听器的接口代码:

    1
    2
    3
    4
    5
    6
    public interface AsyncListener extends EventListener {
        void onComplete(AsyncEvent event) throws IOException;
        void onTimeout(AsyncEvent event) throws IOException;
        void onError(AsyncEvent event) throws IOException;
        void onStartAsync(AsyncEvent event) throws IOException;
    }

    说明:

    1. onStartAsync:异步线程开始时调用
    2. onError:异步线程出错时调用
    3. onTimeout:异步线程执行超时调用
    4. onComplete:异步执行完毕时调用

    一般上,我们在超时或者异常时,会返回给前端相应的提示,比如说超时了,请再次请求等等,根据各业务进行自定义返回。同时,在异步调用完成时,一般需要执行一些清理工作或者其他相关操作。

    需要注意的是只有在调用request.startAsync前将监听器添加到AsyncContext,监听器的onStartAsync方法才会起作用,而调用startAsyncAsyncContext还不存在,所以第一次调用startAsync是不会被监听器中的onStartAsync方法捕获的,只有在超时后又重新开始的情况下onStartAsync方法才会起作用。

    • 设置超时:通过setTimeout方法设置,单位:毫秒。

    一定要设置超时时间,不能无限等待下去,不然和正常的请求就一样了。。

    Servlet方式实现异步请求

    package com.example.demo.Servlet;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.AsyncContext;
    import javax.servlet.AsyncEvent;
    import javax.servlet.AsyncListener;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 使用Servlet方式进行异步请求
     */
    @Slf4j
    @RestController
    public class ServletController {
    
        @RequestMapping(value = "/servlet/orig")
        public void todo(HttpServletRequest request, HttpServletResponse response) throws InterruptedException, IOException {
            response.setContentType("ext/html;charset=UTF-8");
            Thread.sleep(1000);
            response.getWriter().print("这是【正常】的请求返回");
        }
    
        @RequestMapping(value = "/servlet/async")
        public void todoAsync(HttpServletRequest request, HttpServletResponse response) {
            AsyncContext asyncContext = request.startAsync();
            asyncContext.addListener(new AsyncListener() {
                @Override
                public void onComplete(AsyncEvent asyncEvent) throws IOException {
                    log.info("执行完成");
                }
    
                @Override
                public void onTimeout(AsyncEvent asyncEvent) throws IOException {
                    log.info("超时了");
                }
    
                @Override
                public void onError(AsyncEvent asyncEvent) throws IOException {
                    log.info("发生错误");
                }
    
                @Override
                public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
                    log.info("线程开始");
                }
            });
            asyncContext.setTimeout(20000);
            asyncContext.start(() -> {
                try {
                    Thread.sleep(10000);
                    System.out.println("ddddd");
                    log.info("内部线程:"+Thread.currentThread().getName());
                    asyncContext.getResponse().setCharacterEncoding("utf-8");
                    asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
                    asyncContext.getResponse().getWriter().print("这是【异步】的请求返回");
    
                }catch (Exception e){
                    log.error("异常",e);
                }
                //异步请求完成通知
                //此时整个请求才完成
                //其实可以利用此特性 进行多条消息的推送 把连接挂起。。
                asyncContext.complete();
            });
            System.out.println("main方法线程");
            //此时之类 request的线程连接已经释放了
            log.info("线程:" + Thread.currentThread().getName());
        }
    }

    注意:异步请求时,可以利用ThreadPoolExecutor自定义个线程池。

    1.启动下应用,查看控制台输出就可以获悉是否在同一个线程里面了。同时,可设置下等待时间,之后就会调用超时回调方法了。大家可自己试试。

  • 相关阅读:
    2018年春季个人阅读计划
    软件需求我们需要做到什么
    开发日志03
    开发日志02
    开发日志01
    软件需求模式阅读笔记2
    2020/2/11-Python学习计划
    2020/2/10-Python学习计划
    2020/2/9-Python学习计划
    2020/2/8-Python学习计划
  • 原文地址:https://www.cnblogs.com/h-java/p/10275853.html
Copyright © 2020-2023  润新知