知識庫 / Spring / Spring Web RSS 訂閱

一次請求過濾器是什麼?

Spring Web
HongKong
5
12:23 PM · Dec 06 ,2025

1. 概述

在本教程中,我們將學習 OncePerRequestFilter,這是一種在 Spring 中特有的過濾器類型。我們將瞭解它解決的問題,並通過一個簡短的示例來理解如何使用它。

2. 什麼是 OncePerRequestFilter

首先,讓我們瞭解一下過濾器的工作方式。一個 Filter 可以被調用在 servlet 執行之前或之後。當請求被轉發到 servlet 時,RequestDispatcher 可能會將其轉發到另一個 servlet。如果另一個 servlet 也具有相同的 filter,那麼在這些情況下,相同的 filter 會被多次調用。

但是,我們可能希望確保特定的 filter 僅在請求中被調用一次。一個常見的用例是在使用 Spring Security 時。當請求通過 filter 鏈時,我們可能希望某些身份驗證操作僅針對請求一次。

在這種情況下,我們可以擴展 OncePerRequestFilterSpring 保證 OncePerRequestFilter 僅針對給定的請求執行一次。

3. 使用 OncePerRequestFilter 處理同步請求

讓我們通過一個例子來理解如何使用此過濾器。我們將定義一個名為 AuthenticationFilter 的類,該類繼承自 OncePerRequestFilter,並覆蓋 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  " +
                usrName);
        filterChain.doFilter(request, response);
    }
}

由於 OncePerRequestFilter 僅支持 HTTP 請求, 因此在實現 Filter 接口時,我們無需像處理請求和響應對象時那樣進行類型轉換。

4. 使用 OncePerRequestFilter 處理異步請求

對於異步請求,OncePerRequestFilter 默認情況下不會被應用。我們需要覆蓋 shouldNotFilterAsyncDispatch()shouldNotFilterErrorDispatch() 方法,以支持此功能。

有時,我們需要僅在初始請求線程中應用過濾器,而不會在異步調度中創建的附加線程中應用。在其他情況下,我們可能需要在每個附加線程中至少調用一次過濾器。如果是這種情況,我們需要覆蓋 shouldNotFilterAsyncDispatch() 方法。

如果 shouldNotFilterAsyncDispatch() 方法返回 true,則過濾器不會在後續的異步調度中被調用。但是,如果它返回 false,則過濾器將在每個異步調度中被調用,每次一次,每次在線程中。

同樣,我們還會覆蓋 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. 條件跳過請求

我們可以通過覆蓋 shouldNotFilter() 方法,對某些特定請求應用過濾器,而對其他請求則跳過過濾器。

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

6. 快速示例

讓我們來看一個快速示例,以瞭解 <em >OncePerRequestFilter</em> 的行為。
首先,我們將定義一個使用 Spring 的 <em >DeferredResult</em> 異步處理請求的 <em >Controller</em>

@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");
    }
}

當異步處理請求時,線程池中的所有線程都會依次經過相同的過濾器鏈。因此,過濾器會被調用兩次:第一次是在容器線程處理請求時,第二次是在異步調度器完成之後。異步處理完成後,響應會被返回給客户端。

現在,讓我們定義一個實現 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;
    }
}

在上述代碼中,我們故意從 shouldNotFilterAsyncDispatch() 方法中返回 true。 這是為了演示我們的過濾器僅在容器線程中調用一次,而不會為後續的異步線程調用。

讓我們調用我們的端點來演示這一點:

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

輸出:

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

現在,讓我們看看我們想要請求和異步調度程序都調用我們的過濾器的用例。只需通過覆蓋 shouldNotFilterAsyncDispatch() 並返回 false 就可以實現這一點:

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

輸出:

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

我們可以從上面的輸出結果中看到,我們的過濾器被調用了兩次——首先由容器線程調用,然後又由另一個線程調用。

7. 結論

在本文中,我們探討了 OncePerRequestFilter,它解決了什麼問題,以及如何通過一些實際示例來實現它。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.