Javaにおける傍受フィルタパターンの紹介

Javaのインターセプトフィルタパターンの概要

1. 概要

このチュートリアルでは、Intercepting Filter Patternのプレゼンテーション層のコアJ2EEパターンを紹介します。

これは、Pattern Seriesの2番目のチュートリアルであり、here.にあるFront Controller Patternガイドのフォローアップです。

Intercepting Filtersは、着信要求がハンドラーによって処理される前または後にアクションをトリガーするフィルターです。

インターセプトフィルターは、すべてのリクエストに共通であり、既存のハンドラーに影響を与えることなく拡張可能な、Webアプリケーションの集中コンポーネントを表します。

2. ユースケース

前のガイドのexampleを拡張して、an authentication mechanism, request logging, and a visitor counterを実装しましょう。 さらに、ページin various differentencodingを配信する機能が必要です。

これらはすべて、すべてのリクエストに共通であり、ハンドラーから独立している必要があるため、フィルターをインターセプトするユースケースです。

3. フィルター戦略

さまざまなフィルター戦略と使用例の例を紹介します。 Jetty Servletコンテナでコードを実行するには、単に次を実行します:

$> mvn install jetty:run

3.1. カスタムフィルター戦略

カスタムフィルター戦略は、one filter is based on the results of a previous filter in an execution chainの意味で、要求の順序付けられた処理を必要とするすべてのユースケースで使用されます。

これらのチェーンは、FilterChainインターフェイスを実装し、さまざまなFilterクラスを登録することで作成されます。

異なる関心を持つ複数のフィルターチェーンを使用する場合、フィルターマネージャーでそれらを結合できます。

image

 

この例では、ビジターカウンターは、ログインしたユーザーからの一意のユーザー名をカウントすることで機能しています。つまり、認証フィルターの結果に基づいているため、両方のフィルターをチェーンする必要があります。

このフィルターチェーンを実装しましょう。

まず、設定された「username」属性のセッションが存在するかどうかを確認する認証フィルターを作成し、存在しない場合はログイン手順を発行します。

public class AuthenticationFilter implements Filter {
    ...
    @Override
    public void doFilter(
      ServletRequest request,
      ServletResponse response,
      FilterChain chain) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        HttpSession session = httpServletRequest.getSession(false);
        if (session == null || session.getAttribute("username") == null) {
            FrontCommand command = new LoginCommand();
            command.init(httpServletRequest, httpServletResponse);
            command.process();
        } else {
            chain.doFilter(request, response);
        }
    }

    ...
}

それでは、ビジターカウンターを作成しましょう。 このフィルターは、一意のユーザー名のHashSetを維持し、リクエストに「counter」属性を追加します。

public class VisitorCounterFilter implements Filter {
    private static Set users = new HashSet<>();

    ...
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) {
        HttpSession session = ((HttpServletRequest) request).getSession(false);
        Optional.ofNullable(session.getAttribute("username"))
          .map(Object::toString)
          .ifPresent(users::add);
        request.setAttribute("counter", users.size());
        chain.doFilter(request, response);
    }

    ...
}

次に、登録されたフィルターを繰り返し、doFilterメソッドを実行するFilterChainを実装します。

public class FilterChainImpl implements FilterChain {
    private Iterator filters;

    public FilterChainImpl(Filter... filters) {
        this.filters = Arrays.asList(filters).iterator();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
        if (filters.hasNext()) {
            Filter filter = filters.next();
            filter.doFilter(request, response, this);
        }
    }
}

コンポーネントを相互に接続するために、フィルターチェーンのインスタンス化、フィルターの登録、および開始を担当する単純な静的マネージャーを作成しましょう。

public class FilterManager {
    public static void process(HttpServletRequest request,
      HttpServletResponse response, OnIntercept callback) {
        FilterChain filterChain = new FilterChainImpl(
          new AuthenticationFilter(callback), new VisitorCounterFilter());
        filterChain.doFilter(request, response);
    }
}

最後のステップとして、FrontCommand内からリクエスト処理シーケンスの共通部分としてFilterManagerを呼び出す必要があります。

public abstract class FrontCommand {
    ...

    public void process() {
        FilterManager.process(request, response);
    }

    ...
}

3.2. 基本フィルター戦略

このセクションでは、実装されているすべてのフィルターに共通のスーパークラスが使用されるBase Filter Strategy,を示します。

この戦略は、前のセクションのカスタム戦略、または次のセクションで紹介するStandard Filter Strategyとうまく連携します。

抽象基本クラスを使用して、フィルターチェーンに属するカスタム動作を適用できます。 この例では、これを使用して、フィルター構成とデバッグログに関連する定型コードを削減します。

public abstract class BaseFilter implements Filter {
    private Logger log = LoggerFactory.getLogger(BaseFilter.class);

    protected FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("Initialize filter: {}", getClass().getSimpleName());
        this.filterConfig = filterConfig;
    }

    @Override
    public void destroy() {
        log.info("Destroy filter: {}", getClass().getSimpleName());
    }
}

この基本クラスを拡張して、次のセクションに統合されるリクエストログフィルターを作成しましょう。

public class LoggingFilter extends BaseFilter {
    private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    public void doFilter(
      ServletRequest request,
      ServletResponse response,
      FilterChain chain) {
        chain.doFilter(request, response);
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;

        String username = Optional
          .ofNullable(httpServletRequest.getAttribute("username"))
          .map(Object::toString)
          .orElse("guest");

        log.info(
          "Request from '{}@{}': {}?{}",
          username,
          request.getRemoteAddr(),
          httpServletRequest.getRequestURI(),
          request.getParameterMap());
    }
}

3.3. 標準フィルター戦略

フィルタを適用するより柔軟な方法は、Standard Filter Strategyを実装することです。 これは、デプロイメント記述子でフィルターを宣言することにより、またはサーブレット仕様3.0以降、注釈により行うことができます。

標準のフィルター戦略__を使用すると、フィルターマネージャーを明示的に定義しなくても、新しいフィルターを既定のチェーンにプラグインできます。

image

 

フィルタが適用される順序は、アノテーションを介して指定できないことに注意してください。 順序付けされた実行が必要な場合は、デプロイメント記述子に固執するか、カスタムフィルター戦略を実装する必要があります。

基本フィルター戦略も使用する注釈駆動型エンコードフィルターを実装しましょう。

@WebFilter(servletNames = {"intercepting-filter"},
  initParams = {@WebInitParam(name = "encoding", value = "UTF-8")})
public class EncodingFilter extends BaseFilter {
    private String encoding;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        super.init(filterConfig);
        this.encoding = filterConfig.getInitParameter("encoding");
    }

    @Override
    public void doFilter(ServletRequest request,
      ServletResponse response, FilterChain chain) {
        String encoding = Optional
          .ofNullable(request.getParameter("encoding"))
          .orElse(this.encoding);
        response.setCharacterEncoding(encoding);

        chain.doFilter(request, response);
    }
}

デプロイメント記述子を持つサーブレットシナリオでは、web.xmlに次の追加の宣言が含まれます。


    encoding-filter
    
      com.example.patterns.intercepting.filter.filters.EncodingFilter
    


    encoding-filter
    intercepting-filter

サーブレットで使用できるように、ロギングフィルタを取得して注釈を付けましょう。

@WebFilter(servletNames = "intercepting-filter")
public class LoggingFilter extends BaseFilter {
    ...
}

3.4. テンプレートフィルター戦略

Template Filter Strategyは、実装でオーバーライドする必要がある基本クラスで宣言されたテンプレートメソッドを使用することを除いて、基本フィルター戦略とほとんど同じです。

image

 

さらなる処理の前後に呼び出される2つの抽象フィルターメソッドを使用して、基本フィルタークラスを作成しましょう。

この戦略はあまり一般的ではなく、この例では使用しないため、具体的な実装とユースケースはあなたの想像力次第です。

public abstract class TemplateFilter extends BaseFilter {
    protected abstract void preFilter(HttpServletRequest request,
      HttpServletResponse response);

    protected abstract void postFilter(HttpServletRequest request,
      HttpServletResponse response);

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

        preFilter(httpServletRequest, httpServletResponse);
        chain.doFilter(request, response);
        postFilter(httpServletRequest, httpServletResponse);
    }
}

4. 結論

インターセプティングフィルターパターンは、ビジネスロジックとは独立して展開できる横断的な懸念事項をキャプチャします。 ビジネスオペレーションの観点からは、フィルターは事前アクションまたは事後アクションのチェーンとして実行されます。

これまで見てきたように、Intercepting Filter Patternはさまざまな戦略を使用して実装できます。 「現実の」アプリケーションでは、これらの異なるアプローチを組み合わせることができます。

いつものように、ソースon GitHubが見つかります。