フィルター(javax.servlet.Filter)は、各サーブレット共通で前処理・後処理を行いたい場合に使用する。
|
参考
|
package jp.hishidama.sample.filter;
import java.io.IOException; import java.util.Enumeration; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest;
public class SampleFilter implements Filter { @Override public void init(FilterConfig config) throws ServletException { System.out.println("SampleFilter#init()"); } @Override public void destroy() { System.out.println("SampleFilter#destroy()"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("SampleFilter#doFilter() start"); //●前処理 HttpServletRequest req = (HttpServletRequest) request; System.out.println("uri: " + req.getRequestURI()); try { chain.doFilter(request, response); } finally { //●後処理 System.out.println("SampleFilter#doFilter() end"); } } }
フィルターは、Filterインターフェースを実装する。
実装すべきメソッド | 呼ばれるタイミング | 実装すべき内容 |
---|---|---|
init() | JavaEEサーバー(Tomcat等)の起動時 | 一度だけ呼ばれるので、初期処理を記述する。 FilterConfigが引数になっているのはこのメソッドのみなので 初期化パラメーターの取得はここで行う。 |
destroy() | JavaEEサーバー(Tomcat等)の終了時 | 終了時に一度だけ呼ばれる。 何か保持しているデータがあったらここで解放すべきだが 基本的にフィルターで保持するデータで解放が必要なものは無いはず。 |
doFilter() | サーブレットが呼ばれる前 | リクエストにつき1回呼ばれる。 (内部でサーブレットが何回フォワードしても、フィルターは1回のみ) フィルターは複数定義できるので、次のフィルターを実行する為に chain.doFilter()を呼び出す必要がある。 (一番末端のフィルターでサーブレットが呼び出される。 逆に、処理を中断したければchain.doFilter()を呼び出さない→中断方法) chain.doFilter()の前に記述するのが前処理、後に記述すれば後処理になる。 |
<web-app …>
<filter>
<filter-name>sampleFilter</filter-name>
<filter-class>jp.hishidama.sample.filter.SampleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sampleFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
〜
</servlet>
<servlet-mapping>
〜
</servlet-mapping>
〜
</web-app>
web.xmlのfilter-mappingのurl-patternに、フィルターをマッチさせたいURLを書く。
「/*」は、全URLにマッチすることを意味する。
ServletContextは、doFilter()では取得できないが、init()で取得できる。[2009-10-21]
したがって、init()で取得しておいてフィールドに保持しておけば良さそう。
public class SampleFilter implements Filter { private ServletContext context; @Override public void init(FilterConfig config) throws ServletException { this.context = config.getServletContext(); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { dumpActions(context); }
参考: TECHSCOREのフィルタサーブレット
サーブレットと同様に、フィルターもマルチスレッドで呼ばれる。
つまりMTセーフ(スレッドセーフ)な作りにしなければならない。
通常、フィルターでフィールドに保持していいのは、読み取り専用の値、つまりinit()で取得したコンフィグのパラメーターくらいだろう。
web.xmlでフィルターを定義する際に、初期化パラメーターを指定することが出来る。
指定したパラメーターは、init()メソッドで取得することが出来るので、ここでフィールドに入れ直すのだろう。
<filter>
<filter-name>sampleFilter</filter-name>
<filter-class>jp.hishidama.sample.filter.SampleFilter</filter-class>
<init-param>
<param-name>param1</param-name>
<param-value>param1value</param-value>
</init-param>
<init-param>
<param-name>param2</param-name>
<param-value>paramValue2</param-value>
</init-param>
</filter>
public class SampleFilter implements Filter { protected String param1; protected String param2; protected String param3; @Override public void init(FilterConfig config) throws ServletException { @SuppressWarnings("unchecked") Enumeration<String> names = config.getInitParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); String value = config.getInitParameter(name); System.out.printf("%s = %s%n", name, value); } this.param1 = config.getInitParameter("param1"); this.param2 = config.getInitParameter("param2"); this.param3 = config.getInitParameter("param3"); } @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.printf("\t%s %s %s%n", param1, param2, param3); chain.doFilter(request, response); } }
init()メソッドの引数にFilterConfigがあり、そのgetInitParameter()でweb.xmlに書かれたパラメーターの値を取得できる。
パラメーターが記述されていなかった場合はnullになる。
パラメーターは固定(不変)なので、フィールドに保持しておいても大丈夫。
また、丁寧な人ならdestroy()で「param1 = null;」のようにクリーンナップ処理を入れるだろう。
(フィルターのインスタンスが使い回されるなら必須だが…)
web.xmlには複数のフィルター定義を書くことが出来る。
filter-mappingで使用されないフィルターであっても、filter要素が書かれれば、インスタンス自体は作られるようだ。
その際、filter要素に同じクラス名が使われていても、別インスタンスになる。(パラメーターで使い分ける為らしい)
ただし、インスタンス化される順序は不定。(web.xmlに書かれた順序とは関係なさそう)
一方、filter-mappingで複数のフィルターが指定された場合、
web.xmlに記述された順番でフィルターチェインが実行されていくようだ。
フィルター内で何らかの判断(例えばログインチェックによる不正とか)によって処理を中断したい(エラー画面を表示したい)場合は、後続のフィルター処理(chain.doFilter())を呼ばず、自分で別画面へ遷移させる。
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (判定NG) {
RequestDispatcher rd = request.getRequestDispatcher("/error.jsp");
rd.forward(request, response);
return;
}
chain.doFilter(request, response);
}
遷移先はJSPだけでなく、web.xmlに記述された他のURIへ遷移することも出来る。
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (判定NG) {
throw new MyException();
}
chain.doFilter(request, response);
}
〜
</welcome-file-list>
<error-page>
<exception-type>jp.hishidama.sample.MyException</exception-type>
<location>/error.jsp</location>
</error-page>
<jsp-config>
〜
web.xmlでerror-pageを指定すれば、例外をキャッチして、locationで指定したURIへ遷移できる。
しかし、error-pageは最終的に処理しきれなかった例外を遷移させる為にjava.lang.Exceptionを捕捉するような使い方をするべきであり、業務ロジック的な遷移には使わない方がいいと思う。
フィルター処理とStrutsのActionの初期化(厳密にはActionServlet#init()が呼ばれる事)は、JavaEEサーバー起動直後の初回のURIの内容による。
初回のURIがStrutsを使用するものである場合、まずActionServletがインスタンス化されてinit()が呼ばれ、
その後にフィルター処理→ActionServlet#process()処理となる。
一方、初回のURIがStrutsとは無関係(web.xmlの定義のみでの遷移)の場合は、フィルター処理が最初に呼ばれるので、その時点ではStruts関連の初期化は行われていない。
この状態でStrutsのタグ(例えばhtml:errors)を使用したJSPへ遷移しようとすると、ModuleConfigとかが初期化されていないので、ErrorsTagでNullPointerExceptionが発生したりする。
そこで、フィルターでStrutsのメッセージを設定してhtml:errorsを使ったエラー画面へ遷移したい場合は、struts-config.xmlのactionを経由してやればよい。
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (判定NG) {
// html:errorsで表示できるようにエラーメッセージを生成
ActionMessage msg = new ActionMessage("msg.key.filter");
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_MESSAGE, msg);
req.setAttribute(Globals.ERROR_KEY, errors);
RequestDispatcher rd = request.getRequestDispatcher("/strutsError.do");
rd.forward(request, response);
return;
}
chain.doFilter(request, response);
}
<action path="/strutsError" forward="/error.jsp"/>
上記の方式では、リクエストに既にエラーメッセージが入っている場合は、上書きしてしまう。
普通のAction#addErrors()では、既に存在していたら追加したり別の情報も書き換えたりと、いくつかの事を行っている。
したがって、Actionクラスを作って、そこでaddErrors()を呼ぶ方が良さそう。
他にも必要に応じていろいろな処理を行うことも出来るし。
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (判定NG) {
// html:errorsで表示できるようにエラーメッセージを生成
ModuleException ex = new ModuleException("msg.key.filter");
req.setAttribute(SampleFilterAction.EXCEPTION_KEY, ex);
RequestDispatcher rd = request.getRequestDispatcher("/strutsError.do");
rd.forward(request, response);
return;
}
chain.doFilter(request, response);
}
<action path="/strutsError" type="jp.hishidama.sample.filter.SampleFilterAction" forward="/error.jsp"/>
public class SampleFilterAction extends Action {
public static final String EXCEPTION_KEY = "jp.hishidama.sample.filter.SampleFilterAction.EXCEPTION";
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
ModuleException ex = (ModuleException)request.getAttribute(EXCEPTION_KEY);
ActionMessage msg = ex.getActionMessage();
ActionMessages errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_MESSAGE, msg);
addErrors(request, errors);
return new ActionForward(mapping.getForward());
}
}
フィルターからアクションへのデータの受け渡しには、requestのattributeを使う。
データの型は何でもいいので、上記の例ではModuleExceptionを使っている。普通はもっと自分の目的に特化したクラスを作るだろう。
attributeにセットする際のキーは、「javax.servlet.error.
」で始まるキーワードは避けなければならない。
「javax.servlet.error.exception
」はフィルターで発生した例外が保持されるキーなので、ここに例外インスタンスが入っていると
、フィルター処理終了後にエラー処理に行ってしまうっぽい。
他にも「javax
」で始まるキーワードが色々使われているようなので、当然避けるべきだろう。
(Strutsでも「org.apache.struts.
〜」で色々使っている)