はじめに
Spring FrameworkはJavaプラットフォーム上で動作するアプリケーションフレームワークとなり、アプリケーション開発を効率的にできるよう主に下記機能を提供しております。
| 機能 | 詳細 |
|---|---|
| 依存性注入(DI) | クラス外から依存するオブジェクトを注入することで、クラス間が疎結合な設計が可能となり、テストや拡張が容易となる。 |
| アスペクト指向プログラミング(AOP) | ログ出力やトランザクション管理など、異なる関数などで行う共通処理を分離し、一元管理が可能。 |
| データアクセス | データベース操作を簡単に行えるよう、JDBC・JPA・Hibernateなどをサポート。 |
| Webフレームワーク | リクエストのルーティングや画面表示、ビジネスロジックがそれぞれ独立。アノテーションなども充実しており、APIなどを簡単に実装可能。 |
| セキュリティ | 認証・認可機能を提供。また、セッション管理やCSRFなどのWeb攻撃に対する保護機能も提供。 |
また、アプリケーション内の状態や情報を管理を容易にするため様々なコンテキスト(ApplicationContextなど)を提供しており、その中には、実行スレッド内で横断的に参照できる下記のコンテキスト保持クラス(以降、ContextHolder)も提供されています。
| クラス | 概要 |
|---|---|
| RequestContextHolder | Webリクエスト情報を保持 |
| SecurityContextHolder | ログインしたユーザーの認証情報を保持 |
しかし、各種アプリケーション開発では、独自のコンテキスト(以降、カスタムコンテキスト)情報を実行スレッド内で横断的に参照したいケースがあるかと思います。
(関数の引数で引き回したり、参照箇所で都度取得する事も可能ではありますが、保守性や拡張性に乏しい状況になるかと思います。)
当記事ではThreadLocalを使用した「カスタムコンテキストの作成および、利用方法」について紹介いたします。
カスタムコンテキストの作成および、利用方法
今回の例としては、「リクエストを要求したユーザーの詳細情報」を保持するコンテキストの作成および、利用する方法についてコード例を交えて下記の流れでご説明します。
- カスタムコンテキストの定義
- カスタムコンテキストへデータ登録
- カスタムコンテキストの参照
① カスタムコンテキストの定義
UserContextクラス
保持したいユーザーの詳細情報を定義した「UserContext」クラスを作成します。
@Getter
@Builder
public class UserContext {
private String userId;
private String userName;
private String mailAddress;
}
UserContextHolderクラス
ThreadLocal変数を使用して、ユーザー詳細情報を管理する「UserContextHolder」クラスを作成します。
public class UserContextHolder {
private static final ThreadLocal userContext = new ThreadLocal();
public static UserContext get() {
var context = userContext.get();
if (Objects.isNull(context)) {
return UserContext.builder().build();
}
return context;
}
public static void set(UserContext context) {
userContext.set(context);
}
public static void clear() {
userContext.remove();
}
}
② カスタムコンテキストへデータ登録
UserContextFilterの作成
リクエスト単位でユーザーコンテキストを設定する「UserContextFilter」クラスを作成します。
当Filter層にて、リクエスト処理前にユーザーコンテキストを設定し、リクエスト処理後にユーザーコンテキストをクリアしております。
@Component
public class UserContextFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
beforeRequest(request);
filterChain.doFilter(request, response);
} finally {
afterRequest(request);
}
}
private void beforeRequest(HttpServletRequest request) {
// ユーザー情報を設定
var userContext = UserContext.builder()
.userId("★ユーザーID★")
.userName("★ユーザー名★")
.mailAddress("★メールアドレス★")
.build();
UserContextHolder.set(userContext);
}
protected void afterRequest(HttpServletRequest request) {
UserContextHolder.clear();
}
}
UserContextFilterクラスの登録
上記で作成した「UserContextFilter」クラスをDIコンテナへ登録します。
(Filterの実行順番を制御する場合は、「registrationBean.setOrder」を使用してください。)
@RequiredArgsConstructor
@Configuration
public class FilterConfiguration {
private final UserContextFilter requestUserContextFilter;
@Bean
public FilterRegistrationBean registUserContextFilter() {
var registrationBean = new FilterRegistrationBean<>(requestUserContextFilter);
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
③ カスタムコンテキストの参照
「UserContext」を参照したいクラスでの実装例となります。
この例ではCotroller層でユーザーコンテキストを参照しておりますが、Service層やRepository層など、必要な箇所で参照する事が可能です。
@RestController
@RequestMapping("/api")
public class ExampleController extends ControllerParent {
@PostMapping("/example")
public void example(
HttpServletRequest request,
HttpServletResponse response) throws Exception {
// ユーザーコンテキスト取得
var userContext = UserContextHolder.get();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ユーザーコンテキストを使用した処理
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
}
おわりに
今回の例では、実行スレッドで横断的に参照できるカスタムコンテキストの作成方法について紹介させていただきました。
次回は、実行スレッドから別スレッドに対してコンテキストを引き継ぐ方法についてご紹介できればと思います。

