异步处理
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{
…….
}
网友评论