はじめに
ZYYXのアドベントカレンダー企画で6本目の投稿となります
最近知った、Springの共通処理として利用できるInterceptorおよびControllerAdviceが便利と感じたので、備忘録として記します
Interceptor
InterceptorはConfigにて登録した特定のパスのパターンに応じて共通処理を実装することができます
リクエストのログ出力やヘッダーのバリデーションチェックなどを行うのが一般的です
InterceptorではpreHandleの前処理、postHandleの後処理、afterCompletionのレスポンス送信後の処理を実装できます
また、preHandleのみ戻り値がbooleanとなっており、falseを返却すると以降の処理をさせないことができます
(例ではpreHandleのみです)
@Slf4j
public class ApiInterceptor implements HandlerInterceptor {
@Value("${api.key}")
private String apiKey;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// APIキーのチェック
String requestApiKey = request.getHeader("x-api-key");
if (requestApiKey == null || !requestApiKey.equals(apiKey)) {
throw new BadRequestException("不正なAPIキーです");
}
// リクエストURLのログ出力
log.info(request.getRequestURL());
return true;
}
}
@Configuration
@RequiredArgsConstructor
public class InterceptorConfiguration {
private final ApiInterceptor apiInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(apiInterceptor)
.addPathPatterns("/api/**");
}
}
ControllerAdvice
ControllerAdviceでは、コントローラーに対して共通のモデルを追加したり、エラーハンドリングなどができます
Interceptorと違い、パスではなくパッケージ単位で適用するコントローラーを選択できます
個人的にパッケージ単位で適用できる部分が気に入っています
// webのパッケージに適用する
@ControllerAdvice(basePackages = "com.example.controller.web")
public class WebControllerAdvice {
@ModelAttribute("userId")
public String userId(HttpSession session) {
// セッションからユーザーIDを取り出してモデルにセットする
return (String) session.getAttribute("userId");
}
}
こちらの例では、WebのControllerに対してセッションから値を取り出してModelにセットしています
Webページで共通ヘッダーなどにユーザーIDを表示する場合、各Controllerで処理を記述せずともControllerAdviceで共通処理としてModelに値をセットできます
// apiのパッケージに適用する
@ControllerAdvice(basePackages = "com.example.controller.api")
@Slf4j
public class ApiControllerAdvice {
@ExceptionHandler(BadRequestException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleSystem(Exception e) {
return new ErrorResponse(400, "400エラーが発生しました");
}
@ExceptionHandler(InternalServerErrorException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleSystem(Exception e) {
log.error(e)
return new ErrorResponse(500, "500エラーが発生しました");
}
}
public class ErrorResponse {
private final int status;
private final String message;
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
}
}
こちらの例では、APIで発生した例外に対してそのレスポンス形式を統一しています
ExceptionResolverにより、ControllerやInterceptorで発生したExceptionを処理し、実際にHTTPレスポンスとして返却する形式を定義できます
また、エラーごとにログ出力を行うかを定義できます
まとめ
共通処理としてInterceptorおよびControllerAdviceを紹介しましたが、こちらを利用すると各Controllerに逐一実装する必要がなくなり、保守性も高まるので利用するといいと思います
また、やろうと思えばControllerAdviceおよびInterceptorのどちらかだけで実装できてしまうかも知れないですが、各々の責任範囲に伴った共通の処理を実装することも大事かなと思います
Interceptorはリクエストの処理フロー全体に関連する共通処理で、リクエストの検証やログ出力などをするもの、ControllerAdviceはコントローラーに特化した共通処理で例外処理と共通モデルのセットなどを行うものという理解です