Spring Boot におけるインプレメント層別アプローチの比較
Spring Boot 開発において、横断的関心事をどのように扱うかは重要な設計課題となります。単純に HandlerInterceptor を実装するだけでは、複雑な要件に対応できない場合があります。本記事では、フレームワークが提供する異なる境界レベルの 6 つの制御メカニズムを技術的な観点から整理し、それぞれの適用範囲と注意点について解説します。
1. Servlet Filter レベル
最も外部に位置するコンポーネントであり、サーブレットコンテナのライフサイクルに直接組み込まれます。主にセキュリティチェックやロギングなどのグローバルな処理に適しています。
@WebFilter("/*")
public class LatencyLoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(LatencyLoggingFilter.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 duration = System.currentTimeMillis() - startTime;
logger.info("API Response Time: {} ms", duration);
}
}
}
運用上の注意
- この段階では Spring コンテナのスコープ外にあるため、依存注入(DI)を利用するには
WebApplicationContextUtilsを経由する必要があります。 - エラーハンドリングが発生しても必ず
finallyブロックでリソース解放やログ出力を行うよう設計すべきです。
2. HandlerInterceptor
Spring MVC の DispatcherServlet によって制御される層です。コントローラー実行前後のアプローチが可能です。
public class ApiAccessGuard implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
String authHeader = req.getHeader("X-Api-Key");
if (!validateKey(authHeader)) {
res.setStatus(HttpStatus.FORBIDDEN.value());
return false;
}
// メソッド呼び出し前
return true;
}
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView modelAndView) {
if (res != null && !res.isCommitted()) {
// レスポンス書き込み後の修正が可能
}
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ApiAccessGuard())
.addPathPatterns("/secure/**")
.excludePathPatterns("/public/**");
}
}
実装時の制約
postHandleやafterCompletionでは、すでにレスポンスがコミットされている可能性があるため、状態確認が必要です。- 静的リソースへのアクセスも対象となるため、除外パスの定義は必須です。
- 複数のインタセプターが存在する場合、登録順が実行順序に影響します。
3. AOP アスペクト
ビジネスロジックの実装クラスに対して作用します。トランザクション管理やキャッシュ制御など、ドメイン固有の関心事に適しています。
@Aspect
@Component
public class MethodExecutionMonitor {
@Around("@annotation(com.example.cache.EnableCache)")
public Object executeWithCaching(ProceedingJoinPoint joinPoint) throws Throwable {
String signature = joinPoint.getSignature().toShortString();
// キー生成ロジック
String cacheId = "cache:" + signature;
Object cachedData = getFromRedis(cacheId);
if (cachedData != null) {
return cachedData;
}
Object result = joinPoint.proceed();
// 結果保存
saveToRedis(cacheId, result, Duration.ofMinutes(10));
return result;
}
}
機能制限
- Spring が管理している Bean にのみ有効です。
newキーワードで作成されたオブジェクトには作用しません。 @Transactionalとの組み合わせでは、トランザクションアスペクトより先に実行させる必要がある場合、優先度を調整する必要があります。
4. RestTemplate インタセプター
外部システムへのリクエストを送信する際、共通ヘッダーの付与や追跡 ID の追加を実現するためのクライアントサイド機構です。
public class CorrelationIdInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) {
HttpRequest wrappedRequest = new HttpHeadersDecorator(request, headers -> {
headers.set("X-Correlation-ID", UUID.randomUUID().toString());
});
return execution.execute(wrappedRequest, body);
}
}
設定方法
@Bean
public RestTemplate restTemplate(ClientHttpRequestInterceptor correlationInterceptor) {
RestTemplate template = new RestTemplate();
template.setInterceptors(Collections.singletonList(correlationInterceptor));
return template;
}
留意点: 文字列データを送信する際はエンコード形式の確認が必要となり、HTTPS通信の場合、SSLContextの設定が別途必要になることがあります。
5. OpenFeign インタセプター
マイクロサービス間通信において、デクレーティブなHTTP クライアントとして動作する Feign クライアント向けの拡張点です。
public class TenantContextRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
SecurityContext ctx = SecurityContextHolder.getContext();
if (ctx.getAuthentication() != null) {
template.header("Authorization", "Bearer " + ctx.getAuthentication().getToken());
template.header("X-Tenant-ID", ctx.getTenantIdentifier());
}
}
}
構成例:
@Configuration
@FeignClient(name = "user-service")
public class FeignClientsConfig {
@Bean
public RequestInterceptor tenantInterceptor() {
return new TenantContextRequestInterceptor();
}
}
GET リクエストボディの扱いや、フォームデータ送信時には特別なライブラリの併用を検討してください。
6. WebFilter (Spring WebFlux)
リアクティブスタックを用いる WebFlux アプリケーションで使用される非ブロック型のフィルタリング機構です。
@Component
public class GlobalSecurityFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set("Access-Control-Allow-Origin", "*");
return chain.filter(exchange).doOnTerminate(() -> {
// 完了後のクリーンアップ
});
}
}
要件: 従来のサーブレットベースではなく、リアクティブフロー(Mono/Flux)に基づいた処理が前提となります。
各機構の特性比較表
| 種別 | 実行タイミング | オーバーヘッド | 主な用途 |
|---|---|---|---|
| Servlet Filter | 最上位(リクエスト到着時) | 低 | ログ記録、認証基盤、圧縮処理 |
| HandlerInterceptor | MVC フロー内(コントローラー直前) | 中 | ユーザー権限判定、パラメータ変換 |
| AOP | メソッド実行時 | 高 | キャッシュ、トランザクション、監査 |
| RestTemplate | 送信要求時 | 中 | 他システムへのヘッダー付与 |
| Feign | RPC 呼び出し時 | 中 | 分散トレーシング、サービス間認証 |
| WebFilter | リアクティブ全経路 | 極めて高 | CORS、非同期セキュリティ処理 |
パフォーマンスと最適化の原則
- チェーン順序の考慮: 基本的には Filter → Interceptor → AOP の順で実行されます。重い処理は可能な限り手前のレイヤー、または必要最小限の層で行うことが推奨されます。
- 適切なツールの選定: 簡易的な認可は Interceptor で、ビジネスロジックの横断管理は AOP で、マイクロサービス連携は Feign で担当するとアーキテクチャが明確になります。
- 監視の強化: インタセプター自体がボトルネックになる場合があります。Arthas 等のツールを使用して、特定のインタセプターの処理時間を特定しましょう。
# HandlerInterceptor のメソッドトリミング
trace com.example.interceptor.* 'returnObj'
# AOP アスペクトの監視
watch com.example.aspect.*Aspect '#cost>5'