servlet以前的操做同时同步的,就是按照这样的一个流程来走的:html
1.请求根据一个路径路由到一个servlet中,java
2.servlet获取一系列的参数web
3.执行一系列的逻辑(花费时间所占的比重也更大)ajax
4.返回结果异步
上面的问题出如今这一系列的操做都是同步的,因此这个请求一定是堵塞到因此任务都完成以后才返回的,async
这样将会很浪费资源,由于线程堵塞在那里,仅仅是等待任务的完成。可是在servlet3.0以后,咱们基本上能够ide
是这样作的测试
1.请求根据一个路径路由到一个servlet中,url
2.将逻辑放入到异步队列中去spa
3.返回结果
4.异步队列处理任务,得出结果,返回给页面
而servet3.0对于异步的处理主要涉及的有两个特性,一个是新增的类AsyncContext,另外的一个就是asyncSupported属性
①若是咱们想要让咱们的servlet支持异步的话,那么asyncSupported这个属性是必定须要设置的,对于注解的类型来讲,咱们直接设置属性
@WebServlet(asyncSupported=true,urlPatterns={"/async"})
就能够了,对于老版本的配置问价来讲,只须要在配置web.xml 的servlet那里增长一个
<async-supported>true</async-supported>
还有一个就是对于动态的servlet,设置
dynamic.setAsyncSupported(true);
就能够了
②而对于AsyncContext 须要记住的东西仍是蛮多的,可是它主要的是保留了请求和相应的引用,在前面提到的返回结果以后的操做就是经过在异步环境下,对这两个引用进行操做。
要获取这个就须要使用request在3.0以后增长的方法,startAsync(..) ,这个方法就是返回一个AsyncContext实体对象,这里包含了request和response的引用,至于咱们异步的处理方式,就有不少种了,咱们能够直接定义一个工做队列,异步的方式一个个的进行处理,又或者是直接使用AsyncContext.start(Runnable)方法启动一个新的线程去进行处理逻辑
AsyncContext主要的方法:
getRequest() 得到请求即request,咱们能够在异步的环境像在service中使用同样
getReponse() 和上面差很少一个意思
hasOriginalRequestAndResponse()这个方法表示的是咱们使用的AsyncContext是使用原始的请求获取的,仍是经过封装过的请求和相应建立的
简单的讲就是 原始的类型表示的是调用startAsync()。可是封装的就是startAsync(ServletRequest, ServletResponse)或者其余类型啦,
dispatch()方法,这个方法有有好几个重载,表示的是转发,和req.getRequestDispatcher()有点相似,可是比较丰富
若是使用的是startAsync(ServletRequest, ServletResponse)初始化AsyncContext,且传入的请求是HttpServletRequest的一个实例,则使用HttpServletRequest.getRequestURI()返回的URI进行分派。不然分派的是容器最后分派的请求URI。
下面的代码是网上的:
// 请求到 /url/A AsyncContext ac = request.startAsync(); ... ac.dispatch(); // 异步分派到 /url/A // 请求到 /url/A // 转发到 /url/B request.getRequestDispatcher(“/url/B”).forward(request, response); // 从FORWARD的目标内启动异步操做 AsyncContext ac = request.startAsync(); ac.dispatch(); // 异步分派到 /url/A // 请求到 /url/A // 转发到 /url/B request.getRequestDispatcher(“/url/B”).forward(request, response); // 从FORWARD的目标内启动异步操做 AsyncContext ac = request.startAsync(request, response); ac.dispatch(); //异步分派到 /url/B
dispatch(String path) 这个方法就是转发到指定的url上去
complete():在咱们使用了request.startAsync(..)得到AsyncContext以后,在完成异步操做之后,须要调用这个方法结束异步的操做。若是请求分派到一个不支持异步操做的Servlet,或者由AsyncContext.dispatch调用的目标servlet以后没有调用complete,则complete方法会由容器调用。可是对于比合法操做来讲,好比没有调用startAsync放方法,却代用complete() ,那么就会抛出IllegalStateException的异常,同时在调用complete()以前,调用dispath()方法是不起做用的,固然了,由于这个时候异步还没结束嘛,固然不会又什么做用了。
setTimeOut(..) 设置超时的时间 表示的是异步处理的最大时间,若是是一个负数的话,那么表示永远不会超时
start(Runnable run) Runnable表示的就是异步处理的任务。咱们在作的时候 会AsyncContext 带进去 由于因此的操做 都须要依靠他呢
addListener(AsyncListener listener);增长监听器 就是监听AsyncContext各类状态发现变化的,主要有
前面三个都比较好理解,最后异步监听器将以它们添加到请求时的顺序获得通知。
下面是AsyncContext的通常使用方式
package com.hotusm.servlet.async; import java.io.IOException; import java.io.PrintWriter; import java.util.concurrent.TimeUnit; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(urlPatterns={"/url"},asyncSupported=true) public class AsynDemoServlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //resp.setHeader("Connection", "Keep-Alive"); resp.setContentType("text/html;charset=utf-8"); System.out.println(req.isAsyncSupported()+" "+req.isAsyncStarted()); /*req.getAsyncContext(); 表示的是最近的那个被request建立或者是 * 重转发的AsyncContext */ final AsyncContext ac = req.startAsync(); //设置超时的时间 ac.setTimeout(5*1000L); //这种方式 ac.start(new Runnable() { public void run() { try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); } try { PrintWriter writer = ac.getResponse().getWriter(); writer.write("1"); writer.flush(); //这是测试 同一个AsyncContext在没有调用complete 以前能不能屡次的 //调用request 和response PrintWriter writer1 = ac.getResponse().getWriter(); writer1.write("2"); writer1.flush(); ServletRequest request = ac.getRequest(); request.setAttribute("isAsyn", true); /* * 2.在调用完complete以后 表示这个异步已经结束了 若是在调用 * getRequest 或者是getResponse的话 都会抛出IllegalStateException * * */ ac.complete(); } catch (Exception e) { e.printStackTrace(); } } }); //设置监听 ac.addListener(new AsyncListenerImpl()); // 在同一个request中不能同时调用屡次 //req.startAsync(); PrintWriter out = resp.getWriter(); out.write("hello async"); out.write("<br/>"); //调用flush 否则仍是不会输出 由于没有将内容刷出去 out.flush(); } static class AsyncListenerImpl implements AsyncListener{ public void onComplete(AsyncEvent event) throws IOException { System.out.println("onComplete"); } public void onTimeout(AsyncEvent event) throws IOException { System.out.println("onTimeout"); event.getAsyncContext().complete(); } public void onError(AsyncEvent event) throws IOException { System.out.println("onError"); } public void onStartAsync(AsyncEvent event) throws IOException { System.out.println("onStartAsync"); } } }
当咱们上面的url的时候 会立刻返回hello async,而后在大概三秒钟以后,输出12
上面的方式只是使用了start(Runnable run);的方式.咱们也能够将AsyncContext放到一个工做队列中去,而后另外的一个线程池去作处理。
示例代码:
package com.hotusm.servlet.async; import java.io.IOException; import java.io.PrintWriter; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; 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; @WebServlet(urlPatterns={"/async1"},asyncSupported=true) public class AsyncDispatchServlet1 extends HttpServlet{ private LinkedBlockingQueue<AsyncContext> works=new LinkedBlockingQueue<AsyncContext>(100); @Override public void init() throws ServletException {
//由于这里是测试 因此就开了5个线程来进行处理 可是真实的状况下 确定是设计一个伸缩性的方案 new Thread(new HelperWork()).start(); new Thread(new HelperWork()).start(); new Thread(new HelperWork()).start(); new Thread(new HelperWork()).start(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setHeader("Connection", "Keep-Alive"); resp.addHeader("Cache-Control", "private"); resp.addHeader("Pragma", "no-cache"); resp.setContentType("text/html;charset=utf-8"); try { works.put(req.startAsync()); } catch (Exception e) { } PrintWriter writer = resp.getWriter(); writer.write("等待异步完成"); writer.flush(); } private class HelperWork implements Runnable{ public void run() { try { AsyncContext ac = works.take();
//模拟业务消耗
TimeUnit.SECONDS.sleep(2L)
HttpServletRequest request = (HttpServletRequest)ac.getRequest();
Map<String, String[]> maps = request.getParameterMap(); System.out.println(maps); HttpServletResponse response = (HttpServletResponse)ac.getResponse(); PrintWriter writer = response.getWriter(); writer.write(maps.toString()); writer.flush(); ac.complete(); } catch (Exception e) { e.printStackTrace(); } } } }
上面只是一种思路,咱们还能够放入到线程池中进行处理等等。
而后再讲一下怎么经过ajax怎么异步的通讯,咱们只须要在第一次访问servlet的时候,保留AsyncContext的引用,以后经过这个的输出和页面作交互就能够了。