异步处理

2020-09-27  本文已影响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{
…….
}
上一篇下一篇

猜你喜欢

热点阅读