ちょっと限定的な使い方かも知れませんが「仕様変更とかで、全てのコントローラークラスに同じ処理を追加したい。でも、既存モジュールへの改修はコスト的に厳しい」って時に役立つのが、インターセプター。これとスレッドローカルを組み合わせて、なるべく改修範囲を抑えてやりたい事を実現しようと言うのが、今回のコンセプトです。
インターセプターとは
インターセプターは、Spring Bootアプリケーションにおいてリクエストの前後に特定の処理を挿入するための仕組みです。例えば、「ある機能を実行する前に特定の処理を実行したい」場合などに活用されます。また、共通の処理を一箇所で管理することができるため、効率的な開発が可能となります。
スレッドローカルとは
スレッドローカルとは、各スレッドごとに値を保持する仕組みです。これにより、特定のスレッド内で共有するデータを簡単に管理できます。特に、マルチスレッド環境において、スレッドごとに異なる値を保持する必要がある場合に有用です。
サンプルコード
Spring Bootのインターセプターを使用して、スレッドローカルに値を設定して利用するサンプルコードです。DI周りに関しては割愛しますが、基本的には↓のモジュールたちを置いているパスに対してコンポーネントスキャンをするイメージになるかと思います。
スレッドローカル情報の保持クラスを作成
スレッドローカル変数を持ったクラスです。Map形式にしといた方が汎用性が高い気もしていますが、ここではシンプルにString型で定義します。
public class NameContext {
// ThreadLocal変数を定義します。この変数には各スレッドが独自の値を持つことができます。
private static final ThreadLocal<String> currentName = new ThreadLocal<>();
// Nameを返却
public static String getCurrentName() {
return currentName.get();
}
// Nameを設定
public static void setCurrentName(String name) {
currentName.set(name);
}
// Nameを削除
public static void clear() {
currentName.remove();
}
}
インターセプターでスレッドローカルに値を設定
インターセプターの処理内にて、先ほど作成したスレッドローカル変数に値をセットします。このサンプルではリクエストヘッダから値をセットしています。サンプルのHandlerInterceptor .preHandleは、ハンドラ実行前に起動するメソッドです。起動タイミングについては以下の記事を読むと分かりやすいかと思います。
(SpringのHandlerInterceptorの実行順序:https://qiita.com/d-yosh/items/669474b7abc4e9ebf055)
@Component
public class NameInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// リクエストヘッダからName情報を取得
String name = request.getHeader("Name");
// コンテキストにName情報を設定
NameContext.setCurrentName(name);
return true; // 次のインターセプタまたはハンドラを実行するためにtrueを返却
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// リクエストが完了したらコンテキストをクリアします
NameContext.clear();
}
}
インターセプターを登録
WebMvcConfigurerインターフェースのaddInterceptorsを利用し、インターセプターを登録します。
@Configuration
public class WebConfig implements WebMvcConfigurer {
// NameInterceptorのインスタンスを注入
@Autowired
private NameInterceptor nameInterceptor;
// インターセプタ登録
@Override
public void addInterceptors(InterceptorRegistry registry) {
// NameInterceptorをインターセプタに登録
registry.addInterceptor(nameInterceptor);
}
}
値の利用
こんな感じでgetして利用できます。
String currentName = NameContext.getCurrentName();
デメリット
インターセプターとスレッドローカルを組み合わせる方法は、特定のケースで有用ですが、デメリットもあります。例えば、誤った使用方法によるメモリーリークや、意図しない値の共有などが挙げられます。また、過度の使用はコードの可読性や保守性を損なう可能性があります。適切なケースでの利用を心がけることが重要です。
まとめ
初期開発時には利用しないかなあ。。と思いつつも、特定のケースにおいてコレに救われることもあるはず。こういう事もできるよ!ってのを覚えておけば、いざという時に詰まなくて済むかも知れません。使いたくなるのは、やっぱり開発がある程度進んで、戻れなくなっちまってるときかなあ。。