Spring Bootでサーブレットフィルターを使う [Spring Boot 1.x, 2.x両対応]

# 環境
- Spring Boot 1.5.10
- Embedded Tomcat 8.5.27
- JDK 8

> 2018-03-09追記
> Spring Boot 2.0.0.RELEASEにアップグレードしても同様の挙動でした。

> 2018-12-20追記
> Spring Boot 2.1の説明を追記しました。

# 基本的なFilterの登録方法
Spring Bootで用意されている`FilterRegistrationBean`クラスを利用します。
`FilterRegistrationBean`をBean定義すると、Spring BootのAuto Configurationクラス内でFilterが取り出され、Embedded Serverに登録されます。

```java
@Configuration
public class SomeConfig {

    @Bean
    public FilterRegistrationBean hogeFilter() {
        // FilterをnewしてFilterRegistrationBeanのコンストラクタに渡す
        FilterRegistrationBean bean = new FilterRegistrationBean(new HogeFilter());
        // Filterのurl-patternを指定（可変長引数なので複数指定可能）
        bean.addUrlPatterns("/*");
        // Filterの実行順序。整数値の昇順に実行される
        bean.setOrder(Integer.MIN_VALUE);
        return bean;
    }

    @Bean
    public FilterRegistrationBean fugaFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean(new FugaFilter());
        bean.addUrlPatterns("/*");
        // HogeFilterの後に実行される
        bean.setOrder(Integer.MIN_VALUE + 1);
        return bean;
    }

    // 何個でもFilterを定義可能
}
```


# Filter内で他のBeanを使う
`@Bean`メソッドは、引数で他のBeanを受け取ることができます。これは、もともとSpringが持っている機能です。
引数で受け取ったBeanを、Filterのコンストラクタなどで受け取ればOKです。

```java
public class HogeFilter extends OncePerRequestFilter { // もちろん「implements Filter」でもOK
    private final FugaBean fugaBean; // フィールドを作成

    public HogeFilter(FugaBean fugaBean) { // コンストラクタでBeanを受け取る
        this.fugaBean = fugaBean;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 前処理をここに書く
        chain.doFilter(request, response);
        // 後処理をここに書く
    }
}
```

```java
@Configuration
public class HogeConfig {

    @Bean
    public FilterRegistrationBean hogeFilter(FugaBean fugaBean) {
        // FilterのコンストラクタにBeanを渡す
        FilterRegistrationBean bean = new FilterRegistrationBean(new HogeFilter(fugaBean));
        // ...
        return bean;
    }
}
```

FugaBeanは、他のどこか（他のConfigクラスやコンポーネントスキャンなど）でBean定義されている必要があります。

> Filter自体はBeanではないので注意してください。つまり、`@Autowired`でFilterにBeanをDIしたり、AOPをFilterに適用したりすることはできません。

# 全Filterの適用順序を一覧で見る
`FilterRegistrationBean`からFilter取り出し・Embedded Serverへの登録を行っているのは、`ServletContextInitializerBeans`というクラスです。このクラスのDEBUGログ（注: Spring Boot 2.1以降はTRACEログ）を出力すればOKです。
`order`の値の昇順にFilterが実行されます。

```application.properties
# Boot 2.1以降は、debugではなくtraceにする
logging.level.org.springframework.boot.web.servlet.ServletContextInitializerBeans=debug
```

```bash:標準出力
...
2018-02-28 11:58:37.611 DEBUG 4323 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Added existing Filter initializer bean 'loggingFilter1'; order=-2147483648, resource=com.example.Application
2018-02-28 11:58:37.611 DEBUG 4323 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Added existing Filter initializer bean 'loggingFilter2'; order=-2147483647, resource=com.example.Application
2018-02-28 11:58:37.612 DEBUG 4323 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Added existing Filter initializer bean 'loggingFilter3'; order=-2147483646, resource=com.example.Application
2018-02-28 11:58:37.612 DEBUG 4323 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Added existing Servlet initializer bean 'dispatcherServletRegistration'; order=2147483647, resource=class path resource [org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration.class]
2018-02-28 11:58:37.638 DEBUG 4323 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'characterEncodingFilter'; order=-2147483648, resource=class path resource [org/springframework/boot/autoconfigure/web/HttpEncodingAutoConfiguration.class]
2018-02-28 11:58:37.638 DEBUG 4323 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'hiddenHttpMethodFilter'; order=-10000, resource=class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.class]
2018-02-28 11:58:37.638 DEBUG 4323 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'httpPutFormContentFilter'; order=-9900, resource=class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.class]
2018-02-28 11:58:37.638 DEBUG 4323 --- [ost-startStop-1] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'requestContextFilter'; order=-105, resource=class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]
...
```

# Filter自体をBean定義すると？
Filter自体をBean定義してもOKなのですが、url-patternや実行順序を指定できません。`@Bean`メソッドに`@Order`アノテーションを指定しても無視されるようです（`@Order`の値に関わらず、ログに出力されるorderの値が`2147483647`になります）。

```java
    @Bean
    @Order(Integer.MIN_VALUE) // この値は無視されるっぽい（仕様かどうかは不明）
    public HogeFilter hogeFilter() {
        return new HogeFilter();
    }
```

ということで、`FilterRegistrationBean`を使いましょう。

# 参考資料
Spring Boot Reference
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-embedded-container-servlets-filters-listeners-beans
Javadoc
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/web/servlet/FilterRegistrationBean.html