如何创建多个异步java过滤器?

Posted

技术标签:

【中文标题】如何创建多个异步java过滤器?【英文标题】:How to create multiple asynchronous java filters? 【发布时间】:2014-09-04 22:57:15 【问题描述】:

我正在尝试创建一个具有多个异步过滤器的 Java 应用程序,但似乎无法让它们很好地协同工作。我认为主要问题在于 run() 方法,我不知道如何将请求传递给链中的下一个过滤器。我试过chain.doFilter(request, response),但这似乎不起作用,AsyncContext 上有dispatch()complete() API,但它们似乎关闭了整个AsyncContext。似乎必须有另一种方法才能让它发挥作用。下面是我正在使用的过滤器的 sn-p - 第二个过滤器看起来几乎相同。

注意:我正在添加标头以尝试找出被调用的内容。

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException 
        final AsyncContext asyncContext = request.startAsync();
        final HttpServletResponse res = (HttpServletResponse) response;
        asyncContext.addListener(new AsyncListener() 
            @Override
            public void onComplete(AsyncEvent event) throws IOException 
                res.addHeader("S-AST2", "onComplete");
            

            @Override
            public void onTimeout(AsyncEvent event) throws IOException 
                res.addHeader("S-AST3", "onTimeout");
            

            @Override
            public void onError(AsyncEvent event) throws IOException 
                res.addHeader("S-AST4", "onError");
            

            @Override
            public void onStartAsync(AsyncEvent event) throws IOException 
                res.addHeader("S-AST0", "onStartAsync");
            
        );

        asyncContext.start(new Runnable() 
            @Override
            public void run() 
                res.addHeader("S-AST1", "before");
                // This doesn't seem to work...
                asyncContext.dispatch();
                // ... or this ...
                asyncContext.complete();
                // ... or this ...
                chain.doFilter(request, response);
            
        );
    

感谢您的任何见解!

【问题讨论】:

【参考方案1】:

我需要装饰响应,但我不知道底层 servlet 是否正在执行异步,或者它是否已经完成。在 Jetty 9.1.x 上,我通过期待 IllegalStateException 解决了它 以下示例说明了如何包装响应(使用自定义 BufferingHttpServletResponseWrapper 来缓冲所有写入响应的内容)以拦截输入,以便对其进行修饰。

     @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
     final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
     final HttpServletResponse httpServletResponse = (HttpServletResponse) response;

     // Buffer the output to a string in order to calculate its signature and add the signature to a header before it's sent to the client
     final BufferingHttpServletResponseWrapper responseWrapper = new BufferingHttpServletResponseWrapper(httpServletResponse);

     chain.doFilter(httpServletRequest, responseWrapper);

     // This is the only way I can see that will safely let us know if we should treat this as an active async request or not.
     try 
        httpServletRequest.getAsyncContext().addListener(new AsyncListener() 
           @Override
           public void onComplete(AsyncEvent event) throws IOException 
              LOG.debug("onComplete ", event);
              decorateResponse(responseWrapper);
           

           @Override
           public void onTimeout(AsyncEvent event) throws IOException 
              LOG.debug("onTimeout ", event);
           

           @Override
           public void onError(AsyncEvent event) throws IOException 
              LOG.debug("onError ", event);
           

           @Override
           public void onStartAsync(AsyncEvent event) throws IOException 
              LOG.debug("onStartAsync ", event);
              event.getAsyncContext().addListener(this);
           
        
        , httpServletRequest, responseWrapper);
        LOG.debug("After chain.doFilter, async was started");
      catch (IllegalStateException e) 
        LOG.debug("Async not active it appears... ", e.getMessage());
        decorateResponse(responseWrapper);
     
  

【讨论】:

【参考方案2】:

这个答案有两个部分。

1) chain.doFilter(request, response); 仍然是必需的。

2) 这不起作用的原因是,在每个过滤器和 servlet 中,我调用了 request.startAsync(),它启动了一个 new 异步进程,而不是使用现有的进程。因此,如果过滤器启动了一个异步进程,并且 servlet 也启动了一个,它将覆盖/忽略在过滤器中启动的那个。要解决此问题,您必须通过调用request.isAsyncStarted() 检查是否已经启动了异步进程,如果是,则不应启动新的异步上下文,而应使用request.getAsyncContext() 获取现有的。下面是我为每个 servlet 和过滤器创建的帮助器类,这样我就可以调用 AsyncHelper.getAsyncContext(request, response),它会检索现有的 AsyncContext,或者创建一个新的。

public class AsyncHelper 
    public static AsyncContext getAsyncContext(ServletRequest request, ServletResponse response) 
        AsyncContext asyncContext = null;
        if (request.isAsyncStarted()) 
            asyncContext = request.getAsyncContext();
        
        else 
            asyncContext = request.startAsync(request, response);
            asyncContext.setTimeout(2000);
        
        return asyncContext;
    

【讨论】:

阅读 AsyncContext.isAsyncStarted 的 api-docs 看起来这个例子很容易导致 IllegalStateException,这取决于 servlet 正在做什么;在对AsyncContext.isAsyncStartedAsyncContext.getAsyncContext 的调用之间,另一个线程可能通过调用AsyncContext.completedAsyncContext.dispatch 改变了AsyncContext 的状态

以上是关于如何创建多个异步java过滤器?的主要内容,如果未能解决你的问题,请参考以下文章

如何在多个字段上创建远程过滤器? Ext Js 5

如何使用标准构建器使用多个过滤器进行搜索

Eclipse中多个标签的Android LogCat过滤器

如何根据我在多个下拉列表中选择的内容创建过滤器?

Qlikview:如何创建汇总表以过滤多个关联表

动作过滤器:如何调用服务层和异步方法