😽

Re:ゼロから始めるSpring Boot実践#1 WebBlog開発#5 ThreadLocal

に公開

ThreadLocal とは

各スレッドに独立した変数のコピーを持たせることで、スレッドごとに安全にデータを保持する仕組み。

なぜ必要か

通常、複数のスレッドが同じ変数を共有すると、データ競合や予期しない動作を引き起こすことがある。ThreadLocalを使えば、スレッドごとに「専用の変数領域」が与えられるため、他のスレッドに影響されることなく安全にデータを扱える。

public class ThreadLocalTest {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public void doSomething(String name) {
        threadLocal.set(name); // 各スレッド専用に値をセット
        System.out.println("Hello " + threadLocal.get());
        threadLocal.remove(); // メモリリーク防止のため削除
    }
}

ThreadLocalの実装

前回の記事で紹介したコードを使ってThreadLocalを実装する。
前回の記事:https://zenn.dev/articles/cbfc44bc891b18/edit
JWTを解析してユーザ名を取得する処理を共通化し、JWTを解析したデータをThreadLocal保存して利用できるようにする。

ThreadLocalUtilメソッドを作成

ThreadLocalUtilを作成してThreadLocalへのデータ追加、取得、削除機能を提供する。

public class ThreadLocalUtil {
    //ThreadLocalオブジェクトを作成
    private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();

    //キーでバリューを取得
    public static <T> T get(){
        return (T) THREAD_LOCAL.get();
    }
	
    //キーでバリューを保存
    public static void set(Object value){
        THREAD_LOCAL.set(value);
    }


    //メモリリークを防止するため、クリーンする。
    public static void remove(){
        THREAD_LOCAL.remove();
        System.out.println("THREAD_LOCAL" + " : " + THREAD_LOCAL.get()); //本当に削除されたかを確認するため
    }

    public void getClass(Map<String, Object> claims) {
    }
}

ThreadLocalへのデータ保存と削除

前回作成したLoginInterceptorを下記のように改修する。
1.JWTを解析した後のデータをThreadLocalに保存する。
2.リクエストセッションが完了したら、ThreadLocalを削除する。

@Component //自動でインスタンス化し、他クラスに注入できるようにするため。
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
        //tokenを検証
        String token =  request.getHeader("Authorization");
        try {
            Map<String, Object> claims = JwtUtil.parseToken(token);
            //claimsをthreadlocalに保存する ※今回追加部分
            ThreadLocalUtil.set(claims); 
            return true;
        } catch (Exception e) {
            //httpステータスコード401
            response.setStatus(401);
            return false;
        }
    }

    // ↓今回追加部分
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // ThreadLocalをクリーンする
        ThreadLocalUtil.remove();
    }
}

UserControllerの改修

コメントアウトは削除した部分。

    @GetMapping("/userInfo")
    public Result<User> userInfo(/*@RequestHeader(name="Authorization") String token*/){

        //Map<String, Object> map = JwtUtil.parseToken(token);
        Map<String, Object> map = ThreadLocalUtil.get(); //書き換えた部分
        String username = (String) map.get("username"); 
        User user = userService.findByUserName(username);
        return Result.success(user);
    }

ThreadLocal削除の確認

ThreadLocalUtilメソッドで仕込んでコードの実行結果を確認すると、ちゃんと削除されたのを確認できた。

APIテスト

問題なし、記載を省略。

Discussion