一、servlet3.0 异步处理

在 Servlet 3.0 之前,Servlet 采用 Thread-Per-Request 的方式处理请求。即每一次 Http 请求都由某一个线程从头到尾负责处理。

如果一个请求需要进行 IO 操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待 IO 操作完成, 而 IO 操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像 Spring、Struts 这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在 Servlet 之上的。

在以前的 servlet 中,如果作为控制器的 servlet 调用了一个较为耗时的业务方法,则 servlet 必须等到业务执行完后才会生成响应,这使得这次调用成了阻塞式调用,效率比较差。

为了解决这样的问题,Servlet 3.0 引入了异步处理,然后在 Servlet 3.1 中又引入了非阻塞 IO 来进一步增强异步处理的性能。

Servlet3.0 支持异步处理支持,Servlet 接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;接着,Servlet 线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,此时 Servlet 还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有 ServletRequest 和 ServletResponse 对象的引用),或者将请求继续转发给其他 Servlet。

二、同步 Servlet

@WebServlet(value = "/sync")
public class HelloSyncServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(Thread.currentThread()+" start..."+"==>"+System.currentTimeMillis());
        try {
            sayHello();
        } catch (Exception e) {
            e.printStackTrace();
        }
        resp.getWriter().write("hello...");
        System.out.println(Thread.currentThread()+" end..."+"==>"+System.currentTimeMillis());
    }

    public void sayHello() throws Exception {
        System.out.println(Thread.currentThread()+" processing...");
        Thread.sleep(3000);
    }

}

三、配置异步的 servlet 与 filter

(1)通过注解 asyncSupported=true 实现

(2)通过 web.xml 配置

    <servlet>
        <servlet-name>async</servlet-name>
        <servlet-class>com.njf.servlet.AsyncServlet</servlet-class>
        <async-suppored>true</async-suppored>
    </servlet>
    <servlet-mapping>
        <servlet-name>async</servlet-name>
        <url-pattern>/async</url-pattern>
    </servlet-mapping>

对于使用传统的部署描述文件 web.xml 配置 Servlet 和过滤器的情况,Servlet3.0 为<servlet><filter>标签增加了<async-supported>子标签,该标签的默认取值为 false,要启用异步处理支持,则将其设为 true 即可。

对于使用 Servlet3.0 提供的 @WebServlet 和 @WebFilter 进行 Servlet 或过滤器配置的情况,这两个标注都提供了 asyncSupported 属性,默认该属性的取值为 false,要启动异步处理支持,只需将该属性设置为 true 即可。

四、代码示例

示例 1:

@WebServlet(value = "/async", asyncSupported = true//支持异步
public class HelloAsyncServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1、支持异步处理asyncSupported=true
        //2、开启异步模式
        System.out.println("主线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
        AsyncContext startAsync = req.startAsync();

        //3、业务逻辑进行异步处理;开始异步处理
        startAsync.start(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("副线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
                    sayHello();
                    startAsync.complete();
                    //获取到异步上下文
                    AsyncContext asyncContext = req.getAsyncContext();

                    //设置异步调用超时时长
                    asyncContext.setTimeout(3*1000);

                    //异步Servlet里注册异步监听器
                    asyncContext.addListener(new MyAsyncListener());

                    //4、获取响应
                    ServletResponse response = asyncContext.getResponse();
                    PrintWriter writer = response.getWriter();
                    writer.write("hello async...");
                    writer.flush();
                    System.out.println("副线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
                } catch (Exception e) {
                }
            }
        });
        System.out.println("主线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
    }

    public void sayHello() throws Exception {
        System.out.println(Thread.currentThread()+" processing...");
        Thread.sleep(300);
    }

}

示例 2:

@WebServlet(name = "AsyncServlet", urlPatterns = {"/async01"}, asyncSupported = true)
public class AsyncServlet01 extends HttpServlet {

    @Override
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //解决乱码
        request.setCharacterEncoding("GBK");
        response.setContentType("text/html;charset=GBK");

        //通过request获得AsyncContent对象
        AsyncContext actx = request.startAsync(); //重点方法**

        //异步Servlet里注册异步监听器
        actx.addListener(new MyAsyncListener());

        //设置异步调用超时时长
        actx.setTimeout(30 * 3000);
        //启动异步调用的线程
        actx.start(new MyThread(actx));//重点方法**

        // 直接输出到页面的内容(不等异步完成就直接给页面)
        //但这些内容必须放在标签内,否则会在页面输出错误内容,这儿反正我测试是这样,具体不知对不对??
        PrintWriter out = response.getWriter();
        out.println("<h1>不等异步返回结果就直接返到页面的内容</h1>");
        out.flush();
    }

}

//异步处理业务的线程类
class MyThread implements Runnable {
private AsyncContext actx;

    //构造
    public MyThread(AsyncContext actx) {
        this.actx = actx;
    }

    @Override
    public void run() {
        try {
            //等待5秒,模拟处理耗时的业务
            Thread.sleep(4 * 1000);
            //获得request对象,添加数据给页面
            ServletRequest req = actx.getRequest();
            req.setAttribute("content""异步获得的数据");
            //将请求dispath到index.jsp页面,该页面的session必须设为false
            actx.dispatch("/index.jsp");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

示例 3:

@WebServlet(urlPatterns = {"/async02"}, asyncSupported = true)
public class AsyncServlet02 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.println("进入Servlet的时间:" + new Date() + ".");
        out.flush();
        //在子线程中执行业务调用,并由其负责输出响应,主线程退出
        AsyncContext ctx = req.startAsync();
        new Thread(new Task(ctx)).start();
        out.println("结束Servlet的时间:" + new Date() + ".");
        out.flush();
    }
}

class Task implements Runnable {
    private AsyncContext ctx = null;
    public Task(AsyncContext ctx) {
        this.ctx = ctx;
    }
    @Override
    public void run() {
        try {
            //等待10秒钟,以模拟业务方法的执行
            Thread.sleep(10000);
            PrintWriter out = ctx.getResponse().getWriter();
            out.println("业务处理完毕的时间:" + new Date() + ".");
            out.flush();
            ctx.complete();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
posted on 2021-11-23 14:39  格物致知_Tony  阅读(117)  评论(0编辑  收藏  举报