• Servlet3.0的异步


    servlet之前的操作同时同步的,就是按照这样的一个流程来走的:

    1.请求根据一个路径路由到一个servlet中,

    2.servlet获取一系列的参数

    3.执行一系列的逻辑(花费时间所占的比重也更大)

    4.返回结果

    上面的问题出现在这一系列的操作都是同步的,所以这个请求必定是堵塞到所以任务都完成之后才返回的,

    这样将会很浪费资源,因为线程堵塞在那里,仅仅是等待任务的完成。但是在servlet3.0之后,我们基本上可以

    是这样做的

    1.请求根据一个路径路由到一个servlet中,

    2.将逻辑放入到异步队列中去

    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的引用,之后通过这个的输出和页面做交互就可以了。



    
    





  • 相关阅读:
    修理牛棚 贪心 USACO
    零件加工 贪心 题解
    花店橱窗 动态规划 题解
    动态规划 摆花 题解
    NOIP2004普及组第3题 FBI树
    实况世界杯4小游戏链接
    poj2761(treap入门)
    最大连续子序列和(分治法)
    任意区间的最长连续递增子序列,最大连续子序列和
    lca转RMQ
  • 原文地址:https://www.cnblogs.com/zr520/p/6103410.html
Copyright © 2020-2023  润新知