美文网首页
异步处理

异步处理

作者: 码而优则仕 | 来源:发表于2020-09-27 18:50 被阅读0次

    异步处理

    Servlet3 引入了一项新的特性,它可以让Servlet异步处理请求。

    计算机的内存是有限的。Servlet/JSP容器的设计者很清楚这一点,因此他们提供了一些可以进行配置的设置,以确保容器能够在宿主机器中正常运行。例如,在Tomcat7中,处理进来请求的最多线程数量为200.如果是多处理器的服务器,则可以放心地增加线程数量,不过建议还是尽量使用默认值。

    Servlet或Filter一直占用着请求处理线程,知道它完成任务。如果完成任务花费了很长时间,鬓发用户的数量就会超过线程数量,容器将会遇到超出线程的风险。如果发生这种情况,Tomcat就会将超出的请求堆放在一个内部的服务器Socket中(其他容器的处理方式可能会有所不同)。如果继续进来更多的请求,它们将会遭到拒绝,直到有资源可以处理请求为止。

    异步处理特性可以帮助你节省容器线程。这项特性适用于长时间运行的操作。它的工作是等待任务完成,并释放请求处理线程,以便另一个请求能够使用该线程。注意,异步支持只适用于长时间运行的任务,并且你想让用户知道任务的执行结果。如果只是长时间运行的任务,但用户不需要知道处理的结果,那么则只要提供一个Runnable给Executor,并立即返回。例如,如果需要产生一份报表(需要长时间处理),并在报表准备就绪之后通过电子邮件将报表发送出去,那么就不适合使用异步吹特性了。相反,如果需要产生一份报表,并且报表完成之后要展示给用户看,那么就可以使用异步处理。

    WebServlet和WebFilter注解类型可以包含新的asyncSupport属性。为了编写能够支持异步处理的Servlet和Filter,asyncSupport属性必须设置为true。

    @WebServlet(asyncSupport=true)

    @WebFilter(asyncSupport=true)

    或在部署描述符web.xml中配置

    <servlet>
    <async-supported>true</async-supported>
    </servlet>
    

    支持异步处理的Servlet或者Filter可以通过在ServletRequest中调用startAsync方法来启动新的线程。startAsync有两个重载方法:

    public AsyncContext startAsync() throws IllegalStateException;
    
     public AsyncContext startAsync(ServletRequest servletRequest,
                                       ServletResponse servletResponse)
                throws IllegalStateException;
    

    这两个重载方法都返回一个AsyncContext实例,该实例中提供了各种方法,还包含一个ServletRequest和一个ServletResponse。上面的第一个重载方法很简单,也很容易使用。生成的AsyncContext中也将包含原始的ServletRequest和ServletResponse。第二个重载方法运行将原始的ServletRequest和ServletResponse进行包装,并将它们传递给AsyncContext。注意,只能将原始的ServletRequest和ServletResponse或其包装器传递给第二个重载方法。

    注意:重复调用startAsync方法将会返回相同的AsyncContext。如果在不支持异步处理的Servlet或Filter中调用startAsync方法,将会抛出IllegalStateException异常。还要注意的是,AsyncContext的start方法不会造成阻塞,因此,即使它派发的线程还没有启动,也会急促执行下一行代码。

    如果你有一个任务需要相对比较长的时间才能完成,最好创建一个异步的Servlet或Filter。在异步的Servlet或Filter类中需要完成以下工作:

    在ServletRequest中调用startAsync方法。startAsync会返回一个AsyncContext。

    在AsyncContext 中调用 setTimeout方法,设置一个容器必须等待指定任务完成的毫秒数。这个步骤是可选的,但是如果没有设置这个时限,将会采用容器的默认时间。如果任务没能在规定的时限内完成,将会抛出异常。

    调用asyncContext.start 方法,传递一个执行长时间任务的Runnable。

    任务完成时,通过Runnable调用asyncContext.complete方法或者 asyncContext.dispatch方法。

    如下范例是异步Servlet和Filter

    异步Servlet
    package cn.com.yuns.servlet;
    
    import javax.servlet.AsyncContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @author wsq
     * @version AsyncDispatcherServlet.java  2020/7/31  上午7:06 下午
     */
    @WebServlet(name = "AsyncDispatcherServlet", urlPatterns = {"/asyncDispatcher"}, asyncSupported = true)
    public class AsyncDispatcherServlet extends HttpServlet {
    
        private static final long serialVersionUID = 2992L;
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            final AsyncContext asyncContext = request.startAsync();
            request.setAttribute("mainThread", Thread.currentThread().getName());
            //5000 毫秒
            asyncContext.setTimeout(5000);
            //以下模拟长时间的任务--这个长时间的任务不是在主线程中进行的,是在别的线程中进行的
            asyncContext.start(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    request.setAttribute("workThread", Thread.currentThread().getName());
                    // 任务结束之后必须调用  asyncContext.dispatch 或 asyncContext.complete()
                    //要不然,即使任务执行完毕也会等到设置的超时时间之后才结束
                    asyncContext.dispatch("threadNames.jsp");
                }
            });
    
        }
    }
    

    异步监听器

    除了支持Servlet和Filter执行异步操作之外,Servlet3.0还新增了一个AsyncListener接口,以便通知用户在异步处理期间发生的情况。AsyncListener接口定义了如下方法,当某个事件发生时,其中某一个方法就会被调用。

    //一个异步操作完成时调用这个方法
    public void onComplete(AsyncEvent event) throws IOException;
    
    //一个异步操作超时时调用这个方法
        public void onTimeout(AsyncEvent event) throws IOException;
    
    //一个异步操作失败时调用这个方法
        public void onError(AsyncEvent event) throws IOException;
    
    //在刚启动一个异步操作时调用这个方法
        public void onStartAsync(AsyncEvent event) throws IOException;     
    
    

    方法的参数都是 AsyncEvent 事件,可以通过调用 getAsyncContext,getSuppliedRequest 和getSuppliedResponse 方法从中获得相关的 AsyncContext,ServletRequest,ServletResponse实例。与其他Web监听器不同的是,它没有用@WebListener标注 AsyncListener 的实现。

    范例如下:

    package cn.com.yuns.listener;
    
    import javax.servlet.AsyncEvent;
    import javax.servlet.AsyncListener;
    import java.io.IOException;
    
    /**
     * 与其他Web监听器不同的是,它没有用@WebListener标注  AsyncListener 的实现。
     *
     * @author wsq
     * @version MyAsyncListener.java  2020/7/31  上午7:44 下午
     */
    //不需要  @WebListener 标注
    public class MyAsyncListener implements AsyncListener {
    
        /**
         * 异步操作完成时调用这个方法
         *
         * @param asyncEvent
         * @throws IOException
         */
        @Override
        public void onComplete(AsyncEvent asyncEvent) throws IOException {
            System.out.println("onComplete");
        }
    
        /**
         * 异步操作超时时调用这个方法
         *
         * @param asyncEvent 方法的参数都是 AsyncEvent 事件,可以通过调用 getAsyncContext,
         *                   getSuppliedRequest 和getSuppliedResponse 方法从中获得相关的 AsyncContext,
         *                   ServletRequest,ServletResponse实例
         * @throws IOException
         */
        @Override
        public void onTimeout(AsyncEvent asyncEvent) throws IOException {
            System.out.println("onTimeout");
        }
    
        /**
         * 异步操作失败时调用这个方法
         *
         * @param asyncEvent
         * @throws IOException
         */
        @Override
        public void onError(AsyncEvent asyncEvent) throws IOException {
            System.out.println("onError");
        }
    
        /**
         * 刚启动一个异步操作时调用这个方法
         *
         * @param asyncEvent
         * @throws IOException
         */
        @Override
        public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
            System.out.println("onStartAsync");
        }
    }
    

    由于AsyncListener类没有用 @WebListener 进行标注,因此对于你有兴趣收到事件通知的每一个AsyncContext 都需要手动注册一个 AsyncListener,做法是通过在 AsyncContext中调用 addListener 方法来注册一个 AsyncListener:

    void addListener(AsyncListener listener);

    范例如下所示:

    package cn.com.yuns.servlet;
    
    import cn.com.yuns.listener.MyAsyncListener;
    
    import javax.servlet.AsyncContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 使用异步监听器监听
     *
     * @author wsq
     * @version AsyncListenerServlet.java  2020/7/31  上午7:52 下午
     */
    @WebServlet(name = "AsyncListenerServlet", urlPatterns = {"asyncListener"})
    public class AsyncListenerServlet extends HttpServlet {
    
        private static final long serialVersionUID = 2399L;
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            AsyncContext asyncContext = request.startAsync();
            asyncContext.setTimeout(5000);
            //添加异异步监听器
            asyncContext.addListener(new MyAsyncListener());
            asyncContext.start(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    String greeting = "hi form Listener";
                    System.out.println("Wait ......");
                    request.setAttribute("greeting", greeting);
                    asyncContext.dispatch("/test.jsp");
                }
            });
        }
    }
    

    Web注解:

    HandlerTypes

    这个注解类型用来声明ServletContainerInitializer可以处理哪些类型的类。它有一个属性,一个值,用来声明类的类型。例如,下面的ServletContainerInitializer 用 @HandlerTypes 进行标注,申明初始化程序可以处理 UsefulServlet;这些申明的接口的实现类会被自动注入

    @HandlerTypes(UsefulServlet.class)
    public class MyInitializer implements ServletContainerInitializer{
    …….
    }
    

    相关文章

      网友评论

          本文标题:异步处理

          本文链接:https://www.haomeiwen.com/subject/xjferktx.html