异步Servlet与异步Filter

3,004 阅读5分钟

关于servlet异步请求处理介绍参考这个博客点我1点我2,本文代码参考这篇博客点我

异步Servlet

1.创建一个Servlet类

注意:1.开启异步功能必须要设置asyncSupported = true

import com.example.demo.listener.MyAsyncListener;
import com.example.demo.thread.AsyncHandler;

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;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ScheduledThreadPoolExecutor;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * @author zinsanity
 * @date 2020-05-20 16:26
 * @desc
 */
@WebServlet(name = "asyncServlet", urlPatterns = "/asyncServlet", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    ScheduledThreadPoolExecutor userExecutor = new ScheduledThreadPoolExecutor(5);

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        AsyncContext ctx = req.startAsync(req, resp);
        ctx.addListener(new MyAsyncListener());
        userExecutor.execute(new AsyncHandler(ctx));
        resp.getWriter().write("正在执行异步回调");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("AsyncServlet#doPost");
        String body = getRequestBody(req);
        System.out.println("####请求报文="+body);
        // 不加这一行会导致中文乱码
        resp.setContentType("application/json;charset=UTF-8");
        AsyncContext ctx = req.startAsync(req, resp);
        userExecutor.execute(new AsyncHandler(ctx));
        ctx.addListener(new MyAsyncListener());
        resp.getWriter().write("正在执行异步回调");

    }

    private String getRequestBody(HttpServletRequest request) {
        try {
            InputStream inputStream = request.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int x = 0;
            while ((x = inputStream.read()) != -1) {
                baos.write(x);
            }
            baos.flush();
            return new String(baos.toByteArray(), UTF_8);
        } catch (Exception e) {
            System.out.println(e);
        }
        return "";
    }
}

2.创建一个真正执行业务逻辑的线程类

package com.example.demo.thread;

import javax.servlet.AsyncContext;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * @author zinsanity
 * @date 2020-05-20 16:28
 * @desc
 */
public class AsyncHandler implements Runnable {

    private AsyncContext ctx;

    public AsyncHandler(AsyncContext ctx) {
        this.ctx = ctx;
    }

    @Override
    public void run() {
        // 模拟耗时操作
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            PrintWriter pw = ctx.getResponse().getWriter();
            pw.println("done!!!!");
            pw.flush();
            pw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 调用complete方法通知回调,异步处理结束了
        ctx.complete();
    }
}

3.创建一个监听器

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author zinsanity
 * @date 2020-05-20 16:27
 * @desc
 */
public class MyAsyncListener implements AsyncListener {

    @Override
    public void onComplete(AsyncEvent asyncEvent) throws IOException {
            System.out.println("异步servlet【onComplete完成】");
    }

    @Override
    public void onError(AsyncEvent asyncEvent) throws IOException {
        System.out.println("异步servlet错误");
    }

    @Override
    public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
        System.out.println("开始异步servlet");
    }

    @Override
    public void onTimeout(AsyncEvent asyncEvent) throws IOException {
        ServletRequest request = asyncEvent.getAsyncContext().getRequest();
        request.setAttribute("timeout", "true");
        System.out.println("异步servlet【onTimeout超时】");
    }

}

4.执行HTTP请求

http://localhost:8080/asyncServlet

响应结果

正在执行异步回调done!!!!

异步servlet+异步Filter

1.非注解方式创建两个Filter

import org.springframework.core.annotation.Order;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author zinsanity
 * @date 2020-05-14 10:24
 * @desc
 */
public class FilterTest implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("----FilterTest过滤器初始化----");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 对request和response进行一些预处理,比如说对报文的加解密、字符集的变更
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        System.out.println("FilterTest执行前!!!");
        // 让目标资源执行,放行
        filterChain.doFilter(request, response);
        System.out.println("FilterTest执行后!!!");
    }

    @Override
    public void destroy() {
        System.out.println("----FilterTest过滤器销毁----");
    }
}
import org.springframework.core.annotation.Order;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author zinsanity
 * @date 2020-05-14 10:24
 * @desc
 */
public class AsyncFilterTest implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("----AsyncFilterTest过滤器初始化----");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 对request和response进行一些预处理,比如说对报文的加解密、字符集的变更
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        System.out.println("AsyncFilterTest执行前!!!");
        // 让目标资源执行,放行
        filterChain.doFilter(request, response);
        System.out.println("AsyncFilterTest执行后!!!");
    }

    @Override
    public void destroy() {
        System.out.println("----AsyncFilterTest过滤器销毁----");
    }
}

2.创建配置类,配置两个过滤器属性和执行顺序

import com.example.demo.filter.AsyncFilterTest;
import com.example.demo.filter.FilterTest;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.DispatcherType;

/**
 * @author zinsanity
 * @date 2020-05-20 20:09
 * @desc
 */
@Configuration
public class MyFilterConfig {
    @Bean
    public FilterRegistrationBean<FilterTest> filterTestRegistration() {
        FilterRegistrationBean<FilterTest> registration = new FilterRegistrationBean<FilterTest>();
        registration.setFilter(new FilterTest());
        registration.addUrlPatterns("/*");
        registration.setAsyncSupported(true);
        registration.setDispatcherTypes(DispatcherType.REQUEST);
        registration.setOrder(1);
        return registration;
    }

    @Bean
    public FilterRegistrationBean<AsyncFilterTest> asyncFilterTestRegistration() {
        FilterRegistrationBean<AsyncFilterTest> registration = new FilterRegistrationBean<AsyncFilterTest>();
        registration.setFilter(new AsyncFilterTest());
        registration.addUrlPatterns("/*");
        registration.setAsyncSupported(true);
        registration.setDispatcherTypes(DispatcherType.ASYNC);
        registration.setOrder(2);
        return registration;
    }
}

3.filter的asyncSupported属性(setAsyncSupported)

如果异步的servlet和这两个Filter相关联,这两个Filter过滤器也要配置asyncSupported = true(和开启异步的servlet相关的过滤器,一整条链上的都要设置asyncSupported允许才行)。如果异步的servlet前面的某个过滤器没有配置该属性(asyncSupported默认为false),将会如下错误。 java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations.

4.filter的dispatcherTypes(setDispatcherTypes)

根据查询资料,dispatcherTypes可以是一个或者一组dispatcherType,具体取值包括REQUEST、FORWEARD、INCLUDE、ERROR、ASYNC(servelet3.0新增)。 下图来自博客点我

ASYNC,我理解的是只有目标资源通过异步机制调用时,该过滤器将被调用)。为了验证一下,配置AsyncFilterTest过滤器registration.setDispatcherTypes(DispatcherType.ASYNC);发现普通的请求无法经过AsyncFilterTest过滤器。

5.执行HTTP请求

http://localhost:8080/asyncServlet

响应结果如下:

正在执行异步回调done!!!!

日志打印如下:

FilterTest执行前!!!
####请求报文={
	"name": "zinsanity",
	"deviceInfo1": "deviceInfo1"
}
FilterTest执行后!!!
异步servlet【onComplete完成】

从日志中可以看到AsyncFilterTest过滤器没有执行。

6.思考:怎么才能模拟一个异步请求被AsyncFilterTest过滤器拦截呢

(如何模拟这个场景呢?突然想到,如果一个异步的servlet去调用另外一个servlet,这个过滤器是不是就可以用到了。验证如下: 1.再增加一个servlet类

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * @author zinsanity
 * @date 2020-05-20 16:05
 * @desc
 */
@WebServlet(name = "firstServlet", urlPatterns = "/firstServlet", asyncSupported = true)
public class FirstServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("FirstServlet#doGet");
        resp.getWriter().append("firstServlet");
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("FirstServlet#doPost");
        String body = getRequestBody(req);
        System.out.println("####请求报文="+body);
        String result = "{\"age\":\"18\",\"customerId\":\"1111031\",\"name\":\"zhangzaixing\"}";
        PrintWriter out = resp.getWriter();
        out.println(result);

        //释放资源
        out.flush();
        out.close();
        out = null;
/*        resp.getOutputStream().write(result.getBytes("UTF-8"));
        resp.getOutputStream().flush();*/
    }

    private String getRequestBody(HttpServletRequest request) {
        try {
            InputStream v_inputstream = request.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int x = 0;
            while ((x = v_inputstream.read()) != -1) {
                baos.write(x);
            }
            baos.flush();
            return new String(baos.toByteArray(), UTF_8);
        } catch (Exception e) {
            System.out.println(e);
        }
        return "";
    }
}

2.AsyncServlet转发到FirstServlet上面

由于AsyncServlet业务操作交给了另外一个线程A处理,因此转发也应该是A线程去转发,因此修改一下AsyncHandler的run方法

import javax.servlet.AsyncContext;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * @author zinsanity
 * @date 2020-05-20 16:28
 * @desc
 */
public class AsyncHandler implements Runnable {

    private AsyncContext ctx;

    public AsyncHandler(AsyncContext ctx) {
        this.ctx = ctx;
    }

    @Override
    public void run() {
        // 模拟耗时操作
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 注意一定要加/这个字符
        ctx.dispatch("/firstServlet");
    }
}

3.执行HTTP请求

http://localhost:8080/asyncServlet

响应结果如下:

正在执行异步回调{
    "age": "18",
    "customerId": "1111031",
    "name": "zinsanity"
}

日志打印如下:

FilterTest执行前!!!
AsyncServlet#doPost
####请求报文={
	"name": "zinsanity",
	"deviceInfo1": "deviceInfo1"
}
FilterTest执行后!!!
AsyncFilterTest执行前!!!
FirstServlet#doPost
####请求报文=
AsyncFilterTest执行后!!!
异步servlet【onComplete完成】

从日志中可以明确看到,请求进入AsyncServlet时,没有经过AsyncFilterTest过滤器;AsyncServlet转发给FirstServlet时,请求经过了AsyncFilterTest过滤器。

4.注意:AsyncServlet中往流中写用的是resp.getWriter().write("正在执行异步回调");,那么FirstServlet中往流写也要用resp.getWriter().write()。不能用如下写法,会报错。

resp.getOutputStream().write(result.getBytes("UTF-8"));`
resp.getOutputStream().flush();