1. 概要

このチュートリアルでは、Springの特殊なタイプのフィルターであるOncePerRequestFilterについて学習します。 それがどのような問題を解決するかを見て、簡単な例を通してそれを使用する方法を理解します。

2. OncePerRequestFilter とは何ですか?

まず、フィルターがどのように機能するかを理解しましょう。 Filter は、サーブレットの実行前または実行後に呼び出すことができます。 リクエストがサーブレットにディスパッチされると、RequestDispatcherはそれを別のサーブレットに転送する場合があります。 他のサーブレットにも同じフィルタがある可能性があります。 このようなシナリオでは、同じフィルターが複数回呼び出されます。

ただし、特定のフィルターが要求ごとに1回だけ呼び出されるようにする必要がある場合があります。 一般的な使用例は、SpringSecurityを使用する場合です。 リクエストがフィルタチェーンを通過するときに、一部の認証アクションをリクエストに対して1回だけ実行する必要がある場合があります。

このような状況では、OncePerRequestFilterを拡張できます。 Springは、OncePerRequestFilterが特定のリクエストに対して1回だけ実行されることを保証します。

3. 同期リクエストにOncePerRequestFilterを使用する

このフィルターの使用方法を理解するために例を見てみましょう。 OncePerRequestFilter を拡張し、 doFilterInternal()メソッドをオーバーライドするクラスAuthenticationFilterを定義します。

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

OncePerRequestFilter はHTTPリクエストのみをサポートするため、を実装する場合のように、requestおよびresponseオブジェクトをキャストする必要はありません。 ]Filterインターフェース。

4. 非同期リクエストにOncePerRequestFilterを使用する

非同期リクエストの場合、OncePerRequestFilterはデフォルトでは適用されません。 これをサポートするには、メソッド shouldNotFilterAsyncDispatch()および shouldNotFilterErrorDispatch()をオーバーライドする必要があります。

場合によっては、非同期ディスパッチで作成された追加のスレッドではなく、最初の要求スレッドにのみフィルターを適用する必要があります。 また、追加のスレッドごとに少なくとも1回はフィルターを呼び出す必要がある場合もあります。 このような場合、 shouldNotFilterAsyncDispatch()メソッドをオーバーライドする必要があります。

shouldNotFilterAsyncDispatch()メソッドが true を返す場合、フィルターは後続の非同期ディスパッチに対して呼び出されません。 ただし、 false を返す場合、フィルターは非同期ディスパッチごとに、スレッドごとに1回だけ呼び出されます。

同様に、 shouldNotFilterErrorDispatch()メソッドをオーバーライドし、エラーディスパッチをフィルタリングするかどうかに応じてtrueまたはfalseを返します

@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. 簡単な例

OncePerRequestFilterの動作を理解するための簡単な例を見てみましょう。 まず、SpringのDeferredResultを使用してリクエストを非同期的に処理するControllerを定義します。

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

リクエストを非同期で処理する場合、両方のスレッドが同じフィルターチェーンを通過します。 その結果、フィルターは2回呼び出されます。最初はコンテナースレッドが要求を処理するとき、次に非同期ディスパッチャーが完了した後です。 非同期処理が完了すると、応答がクライアントに返されます。

それでは、OncePerRequestFilterを実装するFilterを定義しましょう。

@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

上記の出力から、フィルターが2回呼び出されたことがわかります。最初はコンテナースレッドによって、次に別のスレッドによって呼び出されました。

7. 結論

この記事では、 OncePerRequestFilter 、それが解決する問題、およびいくつかの実用的な例を使用してそれを実装する方法について説明しました。

いつものように、完全なソースコードはGitHubから入手できます。