サーブレット&JSPの基本|サーブレット処理を効率化するフィルタ

毎回書いていた共通処理を1か所にまとめる。フィルタでサーブレットの前後に処理を差し込み、開発を効率化しよう

ServletとJSPでWebアプリケーションを作っていると、複数のサーブレットで同じような処理を書く場面が出てきます。

たとえば、POST送信された日本語のリクエストパラメータを正しく扱うために、サーブレットのdoPostメソッド内でrequest.setCharacterEncoding("UTF-8")を書くことがあります。

フォームを1つだけ作っている段階なら、それほど大変ではありません。

しかし、ログイン処理、投稿処理、申込処理、検索処理、登録処理など、POSTで値を受け取るサーブレットが増えてくると、毎回同じ処理を書くことになります。

同じ処理を何度も書くと、書き忘れが起こりやすくなります。

たとえば、あるサーブレットでは文字コードを指定していたのに、別のサーブレットでは指定を忘れてしまうと、日本語の入力値が文字化けすることがあります。

このような共通処理をまとめて扱うために用意されているのが、フィルタです。

フィルタは、サーブレットのdoGetメソッドやdoPostメソッドが実行される前後に、自動的に処理を差し込める特殊なクラスです。

サーブレット本体に処理を書くのではなく、サーブレットの手前にフィルタを置くことで、文字コード指定、ログイン確認、ログ出力、共通チェックなどをまとめて実行できます。

この記事では、フィルタの基本的な考え方、前処理と後処理、複数のフィルタをつなげるフィルタチェーン、文字コード指定フィルタの作成方法を学習していきます。

フィルタとは

フィルタとは、サーブレットやJSPなどにリクエストが届いたとき、その処理の前後に自動で実行されるクラスです。

通常、ブラウザからサーブレットへリクエストが届くと、サーブレットのdoGetメソッドやdoPostメソッドが実行されます。

フィルタを設定すると、サーブレットが実行される前にフィルタの処理を通すことができます。

さらに、サーブレットの処理が終わったあとに、もう一度フィルタ側へ戻って後処理を行うこともできます。

フィルタの基本的な流れ

順番実行される処理
1フィルタの前処理が実行される
2サーブレットのdoGetまたはdoPostが実行される
3フィルタの後処理が実行される
4レスポンスがブラウザへ返される

フィルタは、サーブレットの処理そのものを置き換えるものではありません。

サーブレットの前後に処理を差し込むための仕組みです。

図1:サーブレットの前後で動作するフィルタ

この図から分かること

フィルタは、ブラウザからのリクエストがサーブレットへ届く前に実行できます。

たとえば、リクエストパラメータの文字コード指定をフィルタに書いておけば、サーブレットのdoPostメソッドが動く前にrequest.setCharacterEncoding("UTF-8")を実行できます。

その結果、それぞれのサーブレットで毎回同じ文字コード指定を書く必要が少なくなります。

サーブレットは本来の処理に集中でき、共通処理はフィルタにまとめられます。

フィルタが役立つ場面

フィルタは、複数のサーブレットで共通して行いたい処理に向いています。

たとえば、文字コード指定は多くのサーブレットで必要になります。

ログイン済みかどうかの確認も、メイン画面、投稿画面、管理画面など、複数のサーブレットで必要になることがあります。

フィルタに向いている処理

処理内容
文字コード指定POST送信された日本語を文字化けしないように扱う
ログイン確認ログインしていない利用者をログイン画面へ戻す
アクセスログ出力どのURLにアクセスされたかを記録する
共通チェックすべてのリクエストに対して同じ確認を行う
後処理サーブレット実行後にログ出力やレスポンス加工を行う

フィルタを使うと、同じ処理を複数のサーブレットに何度も書かずに済みます。

これにより、書き忘れを防ぎやすくなり、プログラム全体の見通しもよくなります。

フィルタを使わない場合の問題

フィルタを使わない場合、共通処理を各サーブレットに直接書くことになります。

たとえば、POST送信を受け取るサーブレットが5つある場合、それぞれのdoPostメソッドにrequest.setCharacterEncoding("UTF-8")を書く必要があります。

フィルタを使わない場合

サーブレット必要な処理
LoginServlet文字コード指定を書く
EntryServlet文字コード指定を書く
ProfileServlet文字コード指定を書く
MessageServlet文字コード指定を書く
CommentServlet文字コード指定を書く

このように同じ処理があちこちに散らばると、あとから修正するときも大変です。

たとえば、共通処理の内容を変えたい場合、複数のファイルを確認して修正しなければなりません。

一方、フィルタを使えば、共通処理を1つのクラスにまとめられます。

複数のサーブレットに共通処理を設定できる

フィルタは、特定のサーブレットだけでなく、複数のサーブレットにまとめて設定できます。

たとえば、ログイン確認フィルタを作成して、ログインが必要なサーブレットに適用するとします。

すると、各サーブレットの中にログイン確認処理を毎回書かなくても、リクエストが届く前にフィルタ側でログイン状態を確認できます。

ログイン確認フィルタのイメージ

状態フィルタの動き
ログイン済みサーブレットへ処理を進める
未ログインログイン画面へリダイレクトする

このように、フィルタはサーブレットの前に立つ受付のような役割を持たせることができます。

サーブレット本体は、ログイン済みであることを前提に処理を書きやすくなります。

図2:共通処理をフィルタにまとめる

この図から分かること

フィルタを使うと、複数のサーブレットに共通する処理を1つのクラスにまとめられます。

たとえば、ログイン確認処理をフィルタに書いておけば、ProfileServlet、MessageServlet、AdminServletなどの各サーブレットに同じ確認処理を書く必要が少なくなります。

未ログインの場合はフィルタでログイン画面へ戻し、ログイン済みの場合だけサーブレットへ処理を進める、という構成にできます。

フィルタチェーンとは

1つのサーブレットに対して、複数のフィルタを設定することもできます。

複数のフィルタが順番に実行される仕組みを、フィルタチェーンと呼びます。

たとえば、次のような順番で処理を通すことができます。

フィルタチェーンの例

順番処理
1文字コード指定フィルタ
2ログ出力フィルタ
3ログイン確認フィルタ
4サーブレット
5ログイン確認フィルタの後処理
6ログ出力フィルタの後処理
7文字コード指定フィルタの後処理

フィルタチェーンでは、サーブレットへ進む前に複数のフィルタを順番に通ります。

サーブレットの処理が終わると、今度は逆向きにフィルタへ戻って後処理を行います。

chain.doFilterの役割

フィルタの中で重要なのが、chain.doFilter(request, response)です。

この処理を実行すると、次のフィルタ、または対象のサーブレットへ処理が進みます。

chain.doFilterの前後

書く場所実行タイミング
chain.doFilter(request, response)より前サーブレット実行前の前処理
chain.doFilter(request, response)次のフィルタまたはサーブレットへ進む
chain.doFilter(request, response)より後サーブレット実行後の後処理

文字コード指定は、サーブレットでrequest.getParameterを実行する前に済ませる必要があります。

そのため、request.setCharacterEncoding("UTF-8")はchain.doFilter(request, response)より前に書きます。

フィルタの作成に必要なもの

フィルタは、HttpFilterクラスを継承して作成します。

また、フィルタとして動かすためにWebFilterアノテーションを付けます。

フィルタ作成の基本

項目内容
アノテーション@WebFilter
継承するクラスHttpFilter
実装するメソッドdoFilter
処理を進める命令chain.doFilter(request, response)
主な用途サーブレットの前処理や後処理

WebFilterアノテーションでは、どのURLに対してフィルタを適用するかを指定します。

たとえば、すべてのリクエストに適用する場合は、/*を指定します。

WebFilterアノテーションで対象を指定する

フィルタは、WebFilterアノテーションで適用先を指定します。

URLパターンを指定することで、どのリクエストに対してフィルタを動かすかを決められます。

WebFilterの指定例

指定適用される対象
@WebFilter("/ProfileServlet")URLパターンが/ProfileServletのサーブレット
@WebFilter("/member/*")/member/から始まるURL
@WebFilter("/*")Webアプリケーション内のすべてのURL

/*を指定すると、すべてのサーブレットだけでなく、JSPファイルやHTMLファイルにもフィルタが適用されます。

そのため、Webアプリケーション全体に共通で行いたい処理を書くときに便利です。

文字コードを指定するフィルタを作成する

ここでは、リクエストパラメータの文字コードをUTF-8に指定するフィルタを作成します。

このフィルタを作成すると、対象となるサーブレットでは、毎回request.setCharacterEncoding("UTF-8")を書く必要が少なくなります。

文字コード指定フィルタの動き

タイミング処理
サーブレット実行前request.setCharacterEncoding("UTF-8")を実行する
その後chain.doFilter(request, response)でサーブレットへ進む
サーブレット側request.getParameterで日本語を取得する

文字コード指定は、request.getParameterより前に行う必要があります。

そのため、フィルタの前処理として書くのが自然です。

リクエストパラメータの文字コードを指定するフィルタの例

ファイル名: SetEncodingFilter.java

package filter;

import java.io.IOException;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebFilter("/*")
public class SetEncodingFilter extends HttpFilter implements Filter {
    private static final long serialVersionUID = 1L;

    public void doFilter(HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {

        // リクエストパラメータの文字コードを指定
        request.setCharacterEncoding("UTF-8");

        // 次のフィルタまたはサーブレットへ処理を進める
        chain.doFilter(request, response);
    }
}

SetEncodingFilter.javaは、filterパッケージに作成します。

WebFilter("/*")を指定しているため、Webアプリケーション内のすべてのURLに対して、このフィルタが適用されます。

doFilterメソッドでは、まずrequest.setCharacterEncoding("UTF-8")を実行しています。

その後、chain.doFilter(request, response)によって、次のフィルタまたは対象のサーブレットへ処理を進めています。

SetEncodingFilter.javaのポイント

SetEncodingFilter.javaでは、サーブレット実行前の前処理として文字コード指定を行っています。

処理のポイント

処理内容
@WebFilter("/*")すべてのURLにフィルタを適用する
extends HttpFilterHTTP用のフィルタとして作成する
doFilterフィルタの処理を書く
request.setCharacterEncoding("UTF-8")リクエストパラメータの文字コードを指定する
chain.doFilter(request, response)次の処理へ進める

request.setCharacterEncoding("UTF-8")をchain.doFilter(request, response)より前に書いている点が大切です。

サーブレット側でrequest.getParameterを実行する前に文字コードを指定しておくことで、日本語の入力値を正しく扱いやすくなります。

図3:文字コード指定フィルタの処理順

この図から分かること

日本語を含むフォーム送信では、サーブレット側でrequest.getParameterを実行する前に文字コードを指定することが大切です。

SetEncodingFilter.javaでは、chain.doFilter(request, response)より前にrequest.setCharacterEncoding("UTF-8")を実行しています。

その後、ProfileServlet.javaへ処理が進むため、ProfileServlet.javaでは文字コード指定を毎回書かなくても、日本語のリクエストパラメータを扱いやすくなります。

フィルタの動作を確認するサーブレットを作成する

次に、フィルタの動作を確認するためのサーブレットを作成します。

今回は、プロフィール登録をイメージしたProfileServlet.javaを作成します。

このサーブレットでは、doGetメソッドで入力フォームを表示し、doPostメソッドで送信された値を受け取って表示します。

文字コード指定はSetEncodingFilter.javaで行うため、ProfileServlet.javaのdoPostメソッドにはrequest.setCharacterEncoding("UTF-8")を書きません。

これにより、フィルタによって文字コード指定が行われていることを確認できます。

フィルタの動作を確認するサーブレットクラスの例

ファイル名: ProfileServlet.java

package servlet;

import java.io.IOException;
import java.io.PrintWriter;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/ProfileServlet")
public class ProfileServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html; charset=UTF-8");

        PrintWriter out = response.getWriter();

        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<head>");
        out.println("<meta charset=\"UTF-8\">");
        out.println("<title>プロフィール登録</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>プロフィール登録</h1>");
        out.println("<form action=\"ProfileServlet\" method=\"post\">");
        out.println("<p>名前:<input type=\"text\" name=\"name\"></p>");
        out.println("<p>学習中の技術:");
        out.println("<input type=\"radio\" name=\"skill\" value=\"Servlet\">Servlet");
        out.println("<input type=\"radio\" name=\"skill\" value=\"JSP\">JSP");
        out.println("<input type=\"radio\" name=\"skill\" value=\"JavaBeans\">JavaBeans");
        out.println("</p>");
        out.println("<p>ひとこと:</p>");
        out.println("<p><textarea name=\"comment\" rows=\"4\" cols=\"40\"></textarea></p>");
        out.println("<input type=\"submit\" value=\"登録\">");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }

    protected void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        // 文字コード指定はSetEncodingFilterで行うため、
        // ここではrequest.setCharacterEncoding("UTF-8")を書かない

        // リクエストパラメータを取得
        String name = request.getParameter("name");
        String skill = request.getParameter("skill");
        String comment = request.getParameter("comment");

        response.setContentType("text/html; charset=UTF-8");

        PrintWriter out = response.getWriter();

        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<head>");
        out.println("<meta charset=\"UTF-8\">");
        out.println("<title>プロフィール登録結果</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>プロフィール登録結果</h1>");
        out.println("<p>名前:" + name + "</p>");
        out.println("<p>学習中の技術:" + skill + "</p>");
        out.println("<p>ひとこと:" + comment + "</p>");
        out.println("<a href=\"ProfileServlet\">入力画面へ戻る</a>");
        out.println("</body>");
        out.println("</html>");
    }
}

ProfileServlet.javaは、servletパッケージに作成します。

プロジェクト名をstudyとしている場合、ブラウザから次のURLで実行できます。

http://localhost:8080/study/ProfileServlet

ブラウザの表示例:プロフィール登録

ブラウザの表示例:プロフィール登録

ProfileServlet.javaのポイント

ProfileServlet.javaでは、doGetメソッドでフォームを表示し、doPostメソッドでフォームから送信された値を取得しています。

ProfileServlet.javaで行っていること

メソッド内容
doGetプロフィール登録フォームを表示する
doPost送信された名前、学習中の技術、ひとことを取得して表示する

doPostメソッドでは、request.getParameterを使って値を取得しています。

しかし、request.setCharacterEncoding("UTF-8")は書いていません。

文字コード指定は、先ほど作成したSetEncodingFilter.javaがサーブレット実行前に行ってくれるためです。

動作確認の手順

SetEncodingFilter.javaとProfileServlet.javaを作成したら、動作を確認します。

確認手順

手順内容
1動的Webプロジェクトstudyにfilterパッケージとservletパッケージを作成する
2filterパッケージにSetEncodingFilter.javaを作成する
3servletパッケージにProfileServlet.javaを作成する
4studyをTomcat11_Java25に追加する
5サーバを起動する
6ブラウザでhttp://localhost:8080/study/ProfileServletへアクセスする
7名前やひとことに日本語を入力して登録する
8結果画面で日本語が正しく表示されることを確認する

確認するときは、ProfileServlet.javaのdoPostメソッドにrequest.setCharacterEncoding("UTF-8")を書いていない点に注目しましょう。

それでも日本語が正しく表示されれば、SetEncodingFilter.javaの前処理が働いています。

フィルタの作り方

フィルタは、次の流れで作成できます。

フィルタ作成の手順

手順内容
1WebFilterアノテーションを付け、適用するURLパターンを指定する
2HttpFilterクラスを継承する
3doFilterメソッドを実装する
4chain.doFilter(request, response)の前後に前処理や後処理を書く

今回のSetEncodingFilter.javaでは、chain.doFilter(request, response)より前にrequest.setCharacterEncoding("UTF-8")を書きました。

これは、サーブレットでリクエストパラメータを取得する前に実行したい処理だからです。

後処理を書きたい場合は、chain.doFilter(request, response)より後に処理を書きます。

Eclipseでフィルタを作成する場合

Eclipseには、フィルタ作成を補助する機能があります。

Eclipseの画面からフィルタを作成すると、WebFilterアノテーション、HttpFilterの継承、doFilterメソッドのひな形などを自動で作成できます。

Eclipseで補助される内容

作業Eclipseの補助
フィルタクラスの作成クラスの基本形を作成できる
WebFilterアノテーションURLパターンを指定して付与できる
HttpFilterの継承必要な継承関係を設定できる
doFilterメソッドひな形を作成できる
import文必要なクラスを自動で追加できる

Eclipseを使うと、メソッド名やimport文の書き間違いを減らしやすくなります。

ただし、フィルタの本質は、chain.doFilter(request, response)の前後にどの処理を書くかです。

JSPファイルやHTMLファイルにもフィルタを適用できる

フィルタは、サーブレットだけに適用されるものではありません。

WebFilterアノテーションで指定したURLパターンに一致すれば、JSPファイルやHTMLファイルにも適用できます。

たとえば、トップ画面のindex.jspだけにフィルタを適用したい場合は、次のように指定できます。

@WebFilter("/index.jsp")

また、すべてのURLに適用したい場合は、次のように指定します。

@WebFilter("/*")

フィルタの適用対象

指定適用対象
@WebFilter("/index.jsp")index.jsp
@WebFilter("/ProfileServlet")ProfileServlet
@WebFilter("/*")サーブレット、JSP、HTMLなどWebアプリケーション内のすべてのURL

今回のSetEncodingFilter.javaでは、@WebFilter("/*")を指定しています。

そのため、ProfileServletだけでなく、Webアプリケーション内のさまざまなリクエストに対してフィルタが動作します。

フィルタを使うときに意識したいこと

フィルタは便利ですが、何でもフィルタに書けばよいというわけではありません。

フィルタには、複数の処理で共通して必要になるものや、サーブレットの前後にまとめて差し込みたい処理を書くのが向いています。

フィルタに向いている処理と向いていない処理

種類
向いている処理文字コード指定、ログイン確認、アクセスログ、共通チェック
向いていない処理特定画面だけの表示内容作成、個別業務処理、1つのサーブレットだけで完結する処理

フィルタに共通処理を任せることで、サーブレット側のコードをすっきりさせられます。

一方で、フィルタに処理を詰め込みすぎると、どこで何が実行されているのか分かりにくくなることもあります。

どのサーブレットに対して、どのタイミングで、何のためにフィルタを適用するのかを意識して使いましょう。

サーブレットとフィルタの役割分担

最後に、サーブレットとフィルタの役割を整理しておきます。

役割の違い

種類主な役割
サーブレットリクエストを受け取り、画面表示や業務処理を行う
フィルタサーブレットの前後に共通処理を差し込む
JSPサーブレットから渡された情報を画面として表示する

サーブレットは、利用者の操作に応じた中心的な処理を担当します。

フィルタは、そのサーブレットの前後で共通処理を担当します。

JSPは、画面表示を担当します。

このように役割を分けることで、Webアプリケーションの処理を整理しやすくなります。

フィルタを使えるようになると、毎回書いていた共通処理をまとめられるため、ServletとJSPを使った開発をより効率よく進められます。