1概要

このチュートリアルでは、「受信フィルタパターン」プレゼンテーション層のコアJ2EEパターンを紹介します。

これは

Pattern Series

の2番目のチュートリアルであり、

Front Controller Pattern

ガイドのフォローアップです。


ここ

.


Intercepting Filters

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

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


2ユースケース

前のガイドからhttps://github.com/eugenp/tutorials/tree/master/patterns/front-controller[example]を拡張し、認証メカニズム、リクエストログ、および訪問者カウンタを実装しましょう。さらに、私たちは私たちのページをさまざまな異なるエンコーディングで配信できることを望んでいます。

これらはすべてすべての要求に共通であり、ハンドラから独立している必要があるため、これらのすべてがフィルタを傍受するための使用例です。


3フィルタ戦略

さまざまなフィルタ戦略と使用例を紹介しましょう。 Jettyサーブレットコンテナでコードを実行するには、単に以下を実行します。

$> mvn install jetty:run


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

カスタムフィルタ戦略は、リクエストの順序処理が必要なすべてのユースケースで使用されます。つまり、

1つのフィルタは、実行チェーン内の前のフィルタの結果に基づいています

これらのチェーンはhttps://tomcat.apache.org/tomcat-7.0-doc/servletapi/javax/servlet/FilterChain.html[

FilterChain

]インターフェースを実装し、それにさまざまな

Filter

クラスを登録することによって作成されます。

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

リンク:/uploads/intercepting

filter-custom

strategy.png%201025w[]

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

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

まず、設定された「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<String> 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<Filter> 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フィルタ戦略

を示します。

この戦略は、前のセクションのカスタム戦略、または次のセクションで紹介する標準フィルタ戦略とうまく連携します。

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

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. 標準フィルタ戦略

フィルタを適用するためのより柔軟な方法は

標準フィルタ戦略

を実装することです。これは、デプロイメント記述子でフィルタを宣言することによって、またはServlet仕様3.0以降は注釈によって行うことができます。

標準的なフィルタ戦略____は、明示的に定義されたフィルタマネージャを使わなくても、新しいフィルタをデフォルトチェーンにプラグインすることを可能にします。

リンク:/uploads/intercepting

filter-standard

strategy.png%20585w[]

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

基本のフィルタ戦略も使用するアノテーションドリブンエンコーディングフィルタを実装しましょう。

@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

に次の追加宣言が含まれます。

<filter>
    <filter-name>encoding-filter</filter-name>
    <filter-class>
      com.baeldung.patterns.intercepting.filter.filters.EncodingFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>encoding-filter</filter-name>
    <servlet-name>intercepting-filter</servlet-name>
</filter-mapping>

サーブレットに慣れるために、ロギングフィルタを選択して注釈を付けましょう。

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


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


Template Filter Strategy

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

リンク:/uploads/intercepting

filter-template

strategy.png%20513w[]

次の処理の前後に呼び出される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結論

インターセプトフィルタパターンは、ビジネスロジックとは無関係に発生する可能性がある分野横断的な懸念を捉えます。ビジネス運用の観点からは、フィルタはプレアクションまたはポストアクションのチェーンとして実行されます。

これまで見てきたように、「受信フィルタパターン」はさまざまな方法で実装できます。 「実社会」のアプリケーションでは、これらの異なるアプローチを組み合わせることができます。

いつものように、あなたはソースhttps://github.com/eugenp/tutorials/tree/master/patterns[

on GitHub

]を見つけるでしょう。