详解Tomcat如何实现异步Servlet。
什么是异步Servlet
Servlet是JavaWeb应用程序中的一种Java类,用于处理接收到的HTTP请求并返回HTTP响应。在Web应用程序的架构中,Servlet在Web服务器(如Tomcat)和Web客户端之间充当中间件的角色。在同步编程模型中,Servlet在完成处理请求后,直接返回结果给Web客户端。这种模型下,客户端在收到响应之前会被一直阻塞。
异步Servlet允许开发者处理请求时不阻塞客户端的请求,而是在请求处理的阶段之间释放HTTP连接,允许服务器继续处理其他请求。异步功能增强了服务器的性能,因为工作线程不会一直处于占用状态。
Tomcat实现异步Servlet的原理
Tomcat通过Servlet 3.0规范中引入的异步Servlet API来实现异步请求处理。异步API是在Servlet 3.0规范中引入的,它允许异步处理的开发人员能够控制HTTP连接什么时候释放,在什么时候响应客户端。
Tomcat的异步API利用了Java NIO(New I/O)库,这意味着终止任务时无需创建任何新的线程。与直接使用线程不同,NIO库允许单个线程在等待基于事件的输入和输出时执行其他任务。Tomcat使用NIO库来实现异步请求处理。当异步请求驱动程序调用ServletResponse的许多的完成处理程序(complete()),Tomcat才会从其EPoll管理器中删除某个管道,从而减少对服务器性能的影响。
异步请求的处理顺序如下:
-
Web容器接收到HTTP请求,创建ServletRequest的实例和ServletResponse的实例
-
Servlet线程开始异步处理请求
-
Servlet在异步线程池中分配一个线程,将处理请求的工作交给此线程
-
Servlet线程完成所有必需的初始化或配置,并调用ServletRequest.startAsync()方法启动异步处理
-
应用程序将ServletRequest.submit() 方法和一个AsyncListener实例或AsyncContext实例一起调用,从而使容器知道异步操作已经完成
-
Servlet线程退出,并将HTTP连接返回到Tomcat
-
Web容器将当前请求上下文保存到内存池中,将HTTP连接返回给Tomcat以便处理其他请求
-
当异步操作完成时,应用程序可以调用AsyncListener的onComplete() 方法或AsyncContext的complete() 方法来通知容器异步操作已经完成,并且在ServletResponse完成响应之前不要释放HTTP连接
-
Web容器的管理员将ServletRequest上下文从内存池中删除,使得与此上下文关联的所有对象可以被垃圾回收
示例1:异步响应
我们可以使用如下代码启用异步响应:
@WebServlet(urlPatterns={"/asyncServlet"})
public class asyncServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try{
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
AsyncContext context = request.startAsync();
ServletOutputStream outStream = response.getOutputStream();
outStream.println("<html><body><h3>异步Servlet示例1:</h3>");
outStream.println("<h3>等待十秒钟</h3>");
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
try{
//模拟长时间运行的代码
Thread.sleep(10000);
//异步操作已经完成
outStream.println("<h3>异步请求已经完成:" + new Date() + "</h3>");
//使用complete方法通知容器异步请求已经完成
context.complete();
}catch(InterruptedException e){
e.printStackTrace();
}
});
outStream.println("</body></html>");
}catch(Exception e){
e.printStackTrace();
}
}
}
在这个例子中,Servlet先输出一些HTML并启用异步模式。接下来,异步操作由Executors类的单线程线程池执行,此操作将模拟需要等待10秒钟的长时间运行过程。当异步操作完成时,将使用AsyncContext的completed()方法通知容器,而不是等待完整的10秒钟才返回响应。请注意:字符串“异步请求已经完成”(Async request complete)的显示时间与输出HTML页面的显示时间不同。
示例2:异步读写文件
下面的示例演示了如何在异步Servlet中读写文件。在这个例子中,我们使用NIO来进行异步文件读取和写入操作。
@WebServlet(urlPatterns={"/asyncServlet2"})
public class AsyncServlet2 extends HttpServlet {
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
final AsyncContext context = request.startAsync(request, response);
final ServletOutputStream outStream = response.getOutputStream();
outStream.println("<html><head>");
outStream.println("<script src='http://cdn.bootcss.com/jquery/2.1.4/jquery.min.js'></script>");
outStream.println("<script>");
outStream.println("function myFunction() {");
outStream.println("$.ajax({");
outStream.println("url: 'getFile',");
outStream.println("type: 'POST',");
outStream.println("success: function(data) {");
outStream.println("var fileName=JSON.parse(data).fileName;");
outStream.println("var fileSize=JSON.parse(data).fileSize;");
outStream.println("$('#fileName').text(fileName);");
outStream.println("$('#fileSize').text(fileSize);");
outStream.println("if(fileSize!=0){");
outStream.println("var download=$('<a>').html('Download File').attr('href','getFileContent');");
outStream.println("$('#fileList').append(download);");
outStream.println("}");
outStream.println("},");
outStream.println("error: function(data) { alert('failed'); }");
outStream.println("});");
outStream.println("}");
outStream.println("</script>");
outStream.println("</head>");
outStream.println("<body onload=\"myFunction()\">");
outStream.println("<h3>异步读写文件</h3>");
outStream.println("<div>");
outStream.println("<p>File name: <label id=\"fileName\"></label></p>");
outStream.println("<p>File size: <label id=\"fileSize\"></label></p>");
outStream.println("<p id=\"fileList\"></p>");
outStream.println("</div></body></html>");
//将工作线程移到独立线程
context.start(new Runnable() {
public void run() {
try{
RandomAccessFile file = new RandomAccessFile("data.txt", "r");
FileChannel fileChannel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1000);
//开始异步读取
fileChannel.read(buffer, fileChannel.position(), buffer,
new CompletionHandler<Integer, ByteBuffer>() {
public void completed(Integer result, ByteBuffer attachment) {
try{
//准备写入:反转缓冲区
attachment.flip();
FileChannel fileChannel2 = new FileOutputStream("data.bak").getChannel();
//开始异步写入
fileChannel2.write(attachment, fileChannel2.size(), attachment,
new CompletionHandler<Integer, ByteBuffer>() {
public void completed(Integer result, ByteBuffer attachment) {
try{
//继续读取数据
if(fileChannel2.size() > fileChannel2.position()){
ByteBuffer buffer = ByteBuffer.allocate(1000);
fileChannel.read(buffer, fileChannel2.position(), buffer, this);
}else{
//使用complete方法通知容器异步请求已经完成
context.complete();
}
}catch(Exception e){
e.printStackTrace();
}
}
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}catch(Exception e){
e.printStackTrace();
}
}
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}catch(Exception e){
e.printStackTrace();
}
}
});
}
}
在这个Servlet中,我们使用ajax技术从服务器获取文件的元数据。在客户端JavaScript代码生成页面时,使用了一个函数,这个函数使用$.ajax()方法调用异步Servlet HTTP文件接口。在异步模式开头,工作线程切换到另一个线程并调用文件处理代码。注意:在这个示例中,异步文件处理包括使用Java NIO异步读取数据和将此数据异步写入到另一个文件。
这就是Tomcat如何实现异步Servlet的完整攻略,希望能对你有所帮助。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解Tomcat是如何实现异步Servlet的 - Python技术站