前回、このブログで「AOP(Aspect Oriented Programming)」について述べていたが、AOPと同じように複数のプログラムに共通する処理を集約する仕組みとして、「サーブレットフィルタ」がある。
AOPでは、特定のJavaメソッドの実行前後に共通する処理を記載していたが、今回の「サーブレットフィルタ」では、フィルタ対象となるコントローラや静的コンテンツをURLパターンで指定することで、これらにアクセスする前後に、共通する処理を集約することができる。
静的コンテンツもフィルタ指定対象にできることが、AOPの場合とは大きく異なっている。フィルタ・AOPが動作する位置については、下記サイトの「共通処理の実装方法」に記載された図が参考になる。
https://qiita.com/kazuki43zoo/items/757b557c05f548c6c5db
今回は、処理の前後にログ出力するサンプルプログラムを通して、「サーブレットフィルタ」の具体的な実装方法について共有する。
前提条件
以下の記事のSpring BootのWEB画面用アプリが作成済であること。
やってみたこと
ログ出力定義の追加
今回はフィルタを利用したログ出力を行うため、Spring BootによるWEB画面作成時に配置されている「application.properties」に、ログ出力定義を追加する。追加後のapplication.propertiesは以下の通り。
1 2 3 4 | server.port = 8084 logging.level.root = INFO logging.level.com.example.demo = DEBUG logging.file = C:/work/logs/demo.log |
上記のうち、下3行がログ出力定義となる。この定義により、demoアプリケーションについてはデバッグレベルのログが、「C:/work/logs/demo.log」というファイルに出力されることになる。
なお、「DemoConfig.java」は、Spring Boot起動時にフィルタオブジェクトを生成する設定を行うクラスで、「DemoFilter1.java」「DemoFilter2.java」はフィルタ定義を行うクラスとなる。その内容については後述する。
フィルタ定義クラスの追加
今回は、2種類のフィルタをサンプルプログラムに追加した。それぞれのフィルタ定義クラスの内容は以下の通りで、Filterインタフェースをimplementsしている。それぞれ、処理の前後にログ出力するようにしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package com.example.demo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.servlet.Filter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.FilterChain; import javax.servlet.ServletException; import java.io.IOException; public class DemoFilter1 implements Filter { //ログ出力のためのクラス private static Log log = LogFactory.getLog(DemoFilter1.class); /** * 処理(本プログラムではコントローラクラスのメソッド)の前後にログ出力を行うフィルタ定義 * @param request サーブレットリクエスト * @param response サーブレットレスポンス * @param chain フィルタチェイン * @throws IOException * @throws ServletException */ @Override public void doFilter(ServletRequest request, ServletResponse response , FilterChain chain) throws IOException, ServletException { log.debug("DemoFilter1 started."); //一連の処理(本プログラムではコントローラクラスのメソッド)を実行 chain.doFilter(request, response); log.debug("DemoFilter1 ended."); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package com.example.demo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.servlet.Filter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.FilterChain; import javax.servlet.ServletException; import java.io.IOException; public class DemoFilter2 implements Filter { //ログ出力のためのクラス private static Log log = LogFactory.getLog(DemoFilter2.class); /** * 処理(本プログラムではコントローラクラスのメソッド)の前後にログ出力を行うフィルタ定義 * @param request サーブレットリクエスト * @param response サーブレットレスポンス * @param chain フィルタチェイン * @throws IOException * @throws ServletException */ @Override public void doFilter(ServletRequest request, ServletResponse response , FilterChain chain) throws IOException, ServletException { log.debug("DemoFilter2 started."); //一連の処理(本プログラムではコントローラクラスのメソッド)を実行 chain.doFilter(request, response); log.debug("DemoFilter2 ended."); } } |
定義ファイルへのフィルタ登録と動作検証
先ほど作成した2種類のフィルタを、「@Configuration」アノテーションをもつ定義ファイルにFilterRegistrationBeanオブジェクトとして追加し、Spring Boot起動時にフィルタオブジェクトが生成されるようにした。そのプログラムの内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | package com.example.demo; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * アプリケーションの設定を行うクラス * 「@Configuration」をクラスに付与し、この中に「@Bean」を付与したメソッドを記述すると、 * アプリケーション起動時に、「@Bean」を付与したメソッドのコンポーネントが作成される */ @Configuration public class DemoConfig { /** * フィルタ1のオブジェクトをコンポーネントに追加 * @return フィルタ1を登録したBean */ @Bean public FilterRegistrationBean demoFilter1(){ //フィルタ1のオブジェクトを1番目に実行するフィルタとして追加 FilterRegistrationBean bean = new FilterRegistrationBean(new DemoFilter1()); //コントローラ・静的コンテンツ全てのリクエストに対してフィルタ1を有効化 bean.addUrlPatterns("/*"); //フィルタ1の実行順序を1に設定 bean.setOrder(1); return bean; } /** * フィルタ2のオブジェクトをコンポーネントに追加 * @return フィルタ2を登録したBean */ @Bean public FilterRegistrationBean demoFilter2(){ //フィルタ2のオブジェクトを2番目に実行するフィルタとして追加 FilterRegistrationBean bean = new FilterRegistrationBean(new DemoFilter2()); //コントローラ・静的コンテンツ全てのリクエストに対してフィルタ2を有効化 bean.addUrlPatterns("/*"); //フィルタ2の実行順序を2に設定 bean.setOrder(2); return bean; } } |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-filter1/demo
その後、Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスした結果は以下の通り。
また、その際のコンソール・ログ出力結果は以下の通りで、下図赤枠部分で、画面遷移時のコントローラ「DemoController.java」呼び出しにより、「DemoFilter1.java」「DemoFilter2.java」で記載したログが出力されているのが確認できる。
●ログファイル(C:/work/logs/demo.log)
要点まとめ
- 「サーブレットフィルタ」を利用すると、特定の静的コンテンツやコントローラに対して、複数のプログラムに共通する処理を集約することができる。
- フィルタクラスは、Filterインタフェースをimplementsすることで作成できる。「chain.doFilter(request, response);」の前後に、集約したい共通処理を実装すればよい。
- Spring Boot起動時にフィルタオブジェクトが生成されるようにするには、クラスの先頭に「@Configuration」アノテーションを付与した定義ファイルに、FilterRegistrationBeanオブジェクトを追加すればよい。その際のフィルタの実行順序は、setOrderメソッドで設定する。