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; } |
说明:
- onStartAsync:异步线程开始时调用
- onError:异步线程出错时调用
- onTimeout:异步线程执行超时调用
- onComplete:异步执行完毕时调用
一般上,我们在超时或者异常时,会返回给前端相应的提示,比如说超时了,请再次请求等等,根据各业务进行自定义返回。同时,在异步调用完成时,一般需要执行一些清理工作或者其他相关操作。
需要注意的是只有在调用request.startAsync
前将监听器添加到AsyncContext
,监听器的onStartAsync
方法才会起作用,而调用startAsync
前AsyncContext
还不存在,所以第一次调用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.启动下应用,查看控制台输出就可以获悉是否在同一个线程里面了。同时,可设置下等待时间,之后就会调用超时回调方法了。大家可自己试试。