What Is OncePerRequestFilter? – 什么是OncePerRequestFilter?

最后修改: 2021年 11月 14日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll learn about OncePerRequestFilter, a special type of filter in Spring. We will see what problem it solves and understand how to use it through a quick example.

在本教程中,我们将学习OncePerRequestFilter,这是Spring中一种特殊的过滤器。我们将看到它解决了什么问题,并通过一个快速的例子了解如何使用它。

2. What Is OncePerRequestFilter?

2.什么是OncePerRequestFilter

Let’s first understand how filters work. A Filter can be called either before or after servlet execution. When a request is dispatched to a servlet, the RequestDispatcher may forward it to another servlet. There’s a possibility that the other servlet also has the same filter. In such scenarios, the same filter gets invoked multiple times.

让我们先了解一下过滤器的工作原理。Filter可以在servlet执行之前或之后被调用。当一个请求被分派到一个servlet时,RequestDispatcher可能会把它转发给另一个servlet。有可能另一个servlet也有同样的过滤器。在这种情况下,相同的过滤器会被多次调用。

But, we might want to ensure that a specific filter is invoked only once per request. A common use case is when working with Spring Security. When a request goes through the filter chain, we might want some of the authentication actions to happen only once for the request.

但是,我们可能想确保一个特定的过滤器在每个请求中只被调用一次。一个常见的用例是在使用Spring Security的时候。当一个请求通过过滤器链时,我们可能希望一些认证动作对请求只发生一次。

We can extend the OncePerRequestFilter in such situations. Spring guarantees that the OncePerRequestFilter is executed only once for a given request.

我们可以在这种情况下扩展OncePerRequestFilterSpring保证OncePerRequestFilter对于一个给定的请求只执行一次。

3. Using OncePerRequestFilter for Synchronous Requests

3.对同步请求使用OncePerRequestFilter

Let’s take an example to understand how to use this filter. We’ll define a class AuthenticationFilter that extends the OncePerRequestFilter, and override the doFilterInternal() method:

让我们举个例子来了解如何使用这个过滤器。我们将定义一个扩展了OncePerRequestFilterAuthenticationFilter类,并重写doFilterInternal()方法。

public class AuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws
            ServletException, IOException {
        String usrName = request.getHeader(“userName”);
        logger.info("Successfully authenticated user  " +
                userName);
        filterChain.doFilter(request, response);
    }
}

Since the OncePerRequestFilter only supports HTTP requests, there’s no need to cast the request and response objects as we do when implementing the Filter interface.

由于OncePerRequestFilter只支持HTTP请求所以不需要像我们实现Filter接口时那样,对requestresponse对象进行铸造。

4. Using OncePerRequestFilter for Asynchronous Requests

4.对异步请求使用OncePerRequestFilter

For asynchronous requests, OncePerRequestFilter doesn’t get applied by default. We need to override the methods shouldNotFilterAsyncDispatch()andshouldNotFilterErrorDispatch() to support this.

对于异步请求,OncePerRequestFilter默认不会被应用。我们需要覆盖shouldNotFilterAsyncDispatch()shouldNotFilterErrorDispatch()方法来支持这一点。

Sometimes, we need the filter applied only in the initial request thread and not in the additional threads created in the async dispatch. Other times, we may need to invoke the filter at least once in each additional thread. In such cases, we need to override the shouldNotFilterAsyncDispatch() method.

有时,我们只需要在初始请求线程中应用过滤器,而不需要在异步调度中创建的附加线程中应用。其他时候,我们可能需要在每个附加线程中至少调用一次过滤器。在这种情况下,我们需要覆盖shouldNotFilterAsyncDispatch()方法。

If the shouldNotFilterAsyncDispatch() method returns true, then the filter will not be called for the subsequent async dispatch. However, if it returns false, the filter will be invoked for each async dispatch, exactly once per thread.

如果shouldNotFilterAsyncDispatch()方法返回true,那么过滤器将不会在随后的异步调度中被调用。然而,如果它返回false,过滤器将为每个异步分派调用,每个线程正好一次。

Similarly, we would override the shouldNotFilterErrorDispatch() method and return true or false, depending on whether we want to filter error dispatches or not:

同样,我们将覆盖shouldNotFilterErrorDispatch()方法并返回truefalse,这取决于我们是否要过滤错误分派

@Component
public class AuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(
      HttpServletRequest request,
      HttpServletResponse response,
      FilterChain filterChain) throws ServletException, IOException {
        String usrName = request.getHeader("userName");
        logger.info("Successfully authenticated user  " +
          usrName);
        filterChain.doFilter(request, response);
    }

    @Override
    protected boolean shouldNotFilterAsyncDispatch() {
        return false;
    }

    @Override
    protected boolean shouldNotFilterErrorDispatch() {
        return false;
    }
}

5. Conditionally Skipping Requests

5.有条件地跳过请求

We can have the filter applied conditionally for some specific requests only and skipped for other requests by overriding the shouldNotFilter() method:

我们可以通过覆盖shouldNotFilter()方法,让过滤器有条件地适用于某些特定的请求,而跳过其他请求。

@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
    return Boolean.TRUE.equals(request.getAttribute(SHOULD_NOT_FILTER));
}

6. Quick Example

6.快速实例

Let’s look at a quick example to understand the behavior of OncePerRequestFilter.
To start with, we’ll define a Controller that processes the request asynchronously using Spring’s DeferredResult:

让我们看一个快速的例子来了解OncePerRequestFilter的行为。
首先,我们将定义一个Controller,使用Spring的DeferredResult异步地处理请求。

@Controller
public class HelloController  {
    @GetMapping(path = "/greeting")
    public DeferredResult<String> hello(HttpServletResponse response) throws Exception {
        DeferredResult<String> deferredResult = new DeferredResult<>();
        executorService.submit(() -> perform(deferredResult));
        return deferredResult;
    }
    private void perform(DeferredResult<String> dr) {
        // some processing 
        dr.setResult("OK");
    }
}

When processing requests asynchronously, both threads go through the same filter chain. Consequently, the filter is called twice: first, when the container thread processes the request, and then after the async dispatcher completes. Once the async processing is completed, the response is returned to the client.

当异步处理请求时,两个线程都要经过同一个过滤器链。因此,过滤器被调用两次:首先,当容器线程处理请求时,然后在异步分配器完成后。一旦异步处理完成,响应就会返回给客户端。

Now, let’s define a Filter implementing OncePerRequestFilter:

现在,让我们定义一个实现OncePerRequestFilterFilter

@Component
public class MyOncePerRequestFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
        logger.info("Inside Once Per Request Filter originated by request {}", request.getRequestURI());
        filterChain.doFilter(request, response);
    }

    @Override
    protected boolean shouldNotFilterAsyncDispatch() {
        return true;
    }
}

In the above code, we have intentionally returned true from the shouldNotFilterAsyncDispatch() method. This is to demonstrate that our filter is invoked only once for the container thread and not for subsequent async threads.

在上面的代码中,我们故意从shouldNotFilterAsyncDispatch()方法返回true。这是为了证明我们的过滤器只为容器线程调用一次,而不是为后续的异步线程调用。

Let’s invoke our endpoint to demonstrate this:

让我们调用我们的端点来证明这一点。

curl -X GET http://localhost:8082/greeting 

Output:

产出:

10:23:24.175 [http-nio-8082-exec-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
10:23:24.175 [http-nio-8082-exec-1] INFO  o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
10:23:24.176 [http-nio-8082-exec-1] INFO  o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
10:23:26.814 [http-nio-8082-exec-1] INFO  c.b.O.MyOncePerRequestFilter - Inside OncePer Request Filter originated by request /greeting

Now, let’s see the case where we want both the request and the async dispatches to invoke our filter. We just need to override shouldNotFilterAsyncDispatch() to return false to achieve this:

现在,让我们看看这样的情况:我们希望请求和异步分派都能调用我们的过滤器。我们只需要覆盖shouldNotFilterAsyncDispatch()以返回false来实现。

@Override
protected boolean shouldNotFilterAsyncDispatch() {
    return false;
}

Output:

产出:

2:53.616 [http-nio-8082-exec-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
10:32:53.616 [http-nio-8082-exec-1] INFO  o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
10:32:53.617 [http-nio-8082-exec-1] INFO  o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
10:32:53.633 [http-nio-8082-exec-1] INFO  c.b.O.MyOncePerRequestFilter - Inside OncePer Request Filter originated by request /greeting
10:32:53.663 [http-nio-8082-exec-2] INFO  c.b.O.MyOncePerRequestFilter - Inside OncePer Request Filter originated by request /greeting

We can see from the above output that our filter got invoked two times — first by the container thread and then by another thread.

我们可以从上面的输出中看到,我们的过滤器被调用了两次–先是被容器线程调用,然后被另一个线程调用。

7. Conclusion

7.结语

In this article, we looked at OncePerRequestFilter, what problems it solves, and how to implement it with some practical examples.

在这篇文章中,我们研究了OncePerRequestFilter,它解决了什么问题,以及如何通过一些实际例子来实现它。

As usual, the complete source code is available over on GitHub.

像往常一样,完整的源代码可以在GitHub上找到