SpringBootのfilterを知る。


## はじめに
筆者はSIerでエンジニアをしています。フロントエンドから入りました。Reactとかやっています。

最近SpringBootを扱っていますが、なんていうのでしょう。SpringBootというフレームワークの上に乗っているという便利さを感じています。

今回はその中でfilterについて触れます。

  - [はじめに](#はじめに)
  - [まずは一番やりたいこと](#まずは一番やりたいこと)
  - [次にやりたいこと](#次にやりたいこと)
  - [前処理、後処理が欲しい](#前処理後処理が欲しい)
  - [フィルターについて](#フィルターについて)
    - [修正前](#修正前)
    - [修正後](#修正後)
  - [こんな使い道](#こんな使い道)
  - [まとめ](#まとめ)
  - [その他](#その他)
    - [なぜあの実装でフィルターになるか](#なぜあの実装でフィルターになるか)
  - [さいごに](#さいごに)


## まずは一番やりたいこと
APIを作りたい。

```java
@RestController
@RequestMapping("/api")
public class SampleApi {

    @GetMapping("/hello")
    public ResponseEntity<String> getHello() {
        return ResponseEntity.ok("Hello, World!");
    }
}
```

## 次にやりたいこと
もう一個APIを作りたくなる。
```java
@RestController
@RequestMapping("/api")
public class SampleApi {

    @GetMapping("/hello")
    public ResponseEntity<String> getHello() {
        return ResponseEntity.ok("Hello, World!");
    }

    @GetMapping("/thanks")
    public ResponseEntity<String> getThanks() {
        return ResponseEntity.ok("Thank you!");
    }
}
```

 さて、こちらをベースに考えていきましょう。

 ## 前処理、後処理が欲しい
 開始、終了のログを出したくなりました。
 SpringBootの機能を使用して、以下のように実装してみます。

 ```java
package com.example.demo.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class SampleApi {
    private static final Logger logger = LoggerFactory.getLogger(SampleApi.class);

    @GetMapping("/hello")
    public ResponseEntity<String> getHello() {
        logger.info("start /api/hello");
        
        ResponseEntity<String> response = ResponseEntity.ok("Hello, World!");
        
        logger.info("end /api/hello");
        return response;
    }

    @GetMapping("/thanks")
    public ResponseEntity<String> getThanks() {
        logger.info("start /api/thanks");
        
        ResponseEntity<String> response = ResponseEntity.ok("Thank you!");
        
        logger.info("end /api/thanks");
        return response;
    }
}
 ```

 まあ、、、悪くないのですが、、、APIの数が100個になったら面倒ですね。
 ここでフィルターの出番です。

## フィルターについて
 フィルターとは、リクエストとレスポンスの前後に共通の処理を挟み込める機能です。たとえば、上記のようなログ出力をすべてのAPIに適用したい場合に便利です。

 ```mermaid
sequenceDiagram
    Client->>+Filter: HTTPリクエスト
    Note right of Filter: 前処理<br/>(例: ログ出力)
    Filter->>+Controller: リクエスト転送
    Controller-->>-Filter: レスポンス
    Note right of Filter: 後処理<br/>(例: ログ出力)
    Filter-->>-Client: HTTPレスポンス
 ```

 具体的な実装例を見てみましょう。

```java
@Component
public class LoggingFilter implements Filter {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String path = httpRequest.getRequestURI();
        
        logger.info("start {}", path);
        
        try {
            chain.doFilter(request, response);
        } finally {
            logger.info("end {}", path);
        }
    }
}
```

これにより、先ほど各APIメソッドに書いていたログ出力の処理を一箇所にまとめることができます。コントローラー側のコードはシンプルなまま、すべてのAPIリクエストに対して自動的にログ出力が行われるようになります。

あらためて、実装が以下のように、ログを一箇所に集約できました。

### 修正前
```mermaid
sequenceDiagram
    title 修正前: 各APIに実装が必要
    Client->>+Controller: /api/hello へリクエスト
    Note right of Controller: start ログ出力
    Note right of Controller: 処理実行
    Note right of Controller: end ログ出力
    Controller-->>-Client: レスポンス
    
    Client->>+Controller: /api/thanks へリクエスト
    Note right of Controller: start ログ出力
    Note right of Controller: 処理実行
    Note right of Controller: end ログ出力
    Controller-->>-Client: レスポンス
```

### 修正後
```mermaid
sequenceDiagram
    title 修正後: フィルターで一括処理
    Client->>+Filter: リクエスト
    Note right of Filter: start ログ出力
    
    alt /api/hello へのリクエスト
        Filter->>+Controller: /api/hello
        Controller-->>-Filter: レスポンス
    else /api/thanks へのリクエスト
        Filter->>+Controller: /api/thanks
        Controller-->>-Filter: レスポンス
    end
    
    Note right of Filter: end ログ出力
    Filter-->>-Client: レスポンス
```

これにより、各APIでログ出力のコードを書く必要がなくなり、フィルター側で一括して処理できるようになったことが視覚的に分かります。APIが増えても、フィルターの実装は1箇所のままで済みます。

## こんな使い道

さて、ログ出力以外にも、フィルターはいろんな場面で活躍します。
例えば

1. **認証周り**
   - ヘッダーからJWTトークンを取り出して検証
   - セッションの確認
   - 権限チェック

2. **パフォーマンス監視**
   ```java
   @Component
   public class PerformanceFilter implements Filter {
       private static final Logger logger = LoggerFactory.getLogger(PerformanceFilter.class);

       @Override
       public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
               throws IOException, ServletException {
           
           long startTime = System.currentTimeMillis();
           
           try {
               chain.doFilter(request, response);
           } finally {
               long endTime = System.currentTimeMillis();
               long processTime = endTime - startTime;
               
               // 1秒以上かかってる処理を見つけたい
               if (processTime > 1000) {
                   logger.warn("ちょっと重いAPIを検出: {}ms", processTime);
               }
           }
       }
   }
   ```

3. **リクエスト/レスポンスの加工**
   - 文字コードの設定
   - ヘッダーの追加
   - レスポンスの圧縮

他にもこんなのをフィルターでまとめます、というケースがあればご教示ください。


## まとめ
SpringBootのフィルターについて見てきました。

- フィルターは共通処理を一箇所にまとめられる
  - 処理の重複を避けられる
  - コードの見通しが良くなる
  - APIの増加に伴うメンテナンスコストを抑えられる

- 主な使い道
  - ログ出力（今回の例）
  - 認証・認可の処理
  - パフォーマンス監視
  - リクエスト/レスポンスの加工

それぞれのcontrollerに実装するのも間違えではありません。でも冗長であり、変更に弱いです。SpringBootのフィルターは、共通の処理をまとめるニーズにぴったりの機能です。

少しでもフィルターについて、理解が深まれば幸いです。

## その他
### なぜあの実装でフィルターになるか

実装のポイントを振り返ってみましょう

```java
@Component
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
```

ここでポイントとなるのが3つです：

1. **@Component というアノテーション**
   - これをつけると、SpringBootがコンポーネントと認識します
   - アプリ起動時に自動的に組み込まれます

2. **implements Filter**
   - これは「フィルターの機能を持つよ」という印です
   - SpringBootは「フィルターだね、じゃあリクエストが来たら通すよ」と理解します

3. **doFilter メソッド**
   - ここが実際の処理を書く場所
   - `chain.doFilter()`を境に、前と後ろに処理が書けます
   - リクエストが来たら、上から順番に実行されます

シンプルですが、これだけの実装でフィルターとして機能します。
SpringBootが「フィルターの仕組み」を用意してくれているおかげですね。

## さいごに

てことで、フレームワークのもとでの開発はとーーーっても便利ですね。
一つずつ理解を深めていきます。