Filter・Interceptor・AOP の違いは「どこで動き、何が見えるか」です。Filter は DispatcherServlet の手前で HTTP のリクエストとレスポンスそのものを扱い、HandlerInterceptor は DispatcherServlet の中で「どのコントローラーのメソッドが呼ばれるか」を知ったうえで前後に入り、AOP は Spring の Bean のメソッド呼び出しに入って引数と戻り値を扱います。

この記事では、同じ「処理時間をログに出す」処理を3通りで書いたコード全文、@ControllerAdvice と Spring Security のフィルターも加えた比較表、リクエストが通る順番の図、用途別の選び方を1ページにまとめました。

確認環境:Spring Boot 4.1.1/Spring Framework 7.0.9/Spring Security 7.1.1 の公式リファレンスと Javadoc で確認(確認日 2026年10月7日)。実行順は公式ドキュメントの記述に基づいて示しています。

Filter・Interceptor・AOP の早見表

仕組み 動く位置 見えるもの 向いている用途
Filter DispatcherServlet の手前 HTTP のリクエスト・レスポンス 文字コード、CORS、全リクエストのログ
Spring Security のフィルター Filter の並びの中 リクエストと認証情報 認証、URL 単位の認可
HandlerInterceptor DispatcherServlet の中、ハンドラーの前後 リクエストとハンドラー(メソッド情報) コントローラー単位の前処理・計測
@ControllerAdvice コントローラーの周辺 例外、モデル、バインダー 例外を共通のレスポンスに変換
AOP(@Aspect) Bean のメソッド呼び出し 引数、戻り値、例外 サービス層のログ、監査、計測

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Spring Filter・Interceptor・AOPの違い|比較表と実行順」

同じ「処理時間のログ」を3通りで書く

題材をそろえると、それぞれが受け取れる情報の違いがはっきりします。パッケージは com.example.demo としています。

依存関係(Spring Boot 4.x・Maven)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>
    <!-- @Aspect を使う場合 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aspectj</artifactId>
    </dependency>
</dependencies>

Spring Boot 4.0 の移行ガイドでは、spring-boot-starter-web は spring-boot-starter-webmvc に、spring-boot-starter-aop は spring-boot-starter-aspectj に名前が変わったと案内されています。3.x の場合は変更前の名前を使います。

1. Filter(OncePerRequestFilter)

package com.example.demo.web;

import java.io.IOException;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
public class TimingFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(TimingFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        long start = System.nanoTime();
        try {
            filterChain.doFilter(request, response); // 次のフィルター、最後は DispatcherServlet へ
        } finally {
            long ms = (System.nanoTime() - start) / 1_000_000;
            log.info("[Filter] {} {} status={} {}ms",
                    request.getMethod(), request.getRequestURI(), response.getStatus(), ms);
        }
    }

    /** 対象から外したいパスをここで指定する */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        return request.getRequestURI().startsWith("/assets/");
    }
}

OncePerRequestFilter は、Javadoc によると「1回のリクエストのディスパッチにつき1度だけ実行されることを保証するためのフィルターの基底クラス」です。shouldNotFilter の既定は false(すべて対象)で、非同期ディスパッチとエラーディスパッチでは既定で実行されません(shouldNotFilterAsyncDispatch・shouldNotFilterErrorDispatch の既定が true)。

Filter から見えるのは URL・ヘッダー・ステータスなど HTTP の情報で、どのコントローラーが呼ばれたかはわかりません。

Filter の登録方法

Spring Boot では、Filter を Bean として宣言すれば Boot が組み込みコンテナに登録します。上のように @Component を付けるのがいちばん短い方法です。URL パターンや順序を細かく決めたいときは、@Component を外して FilterRegistrationBean で登録します。

package com.example.demo.config;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

import com.example.demo.web.TimingFilter;

@Configuration(proxyBeanMethods = false)
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<TimingFilter> timingFilterRegistration() {
        FilterRegistrationBean<TimingFilter> registration =
                new FilterRegistrationBean<>(new TimingFilter());
        registration.addUrlPatterns("/api/*");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 10);
        return registration;
    }
}

2. HandlerInterceptor + WebMvcConfigurer

package com.example.demo.web;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

public class TimingInterceptor implements HandlerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(TimingInterceptor.class);
    private static final String START_ATTR = TimingInterceptor.class.getName() + ".start";

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {
        request.setAttribute(START_ATTR, System.nanoTime());
        return true; // false を返すと、ここで処理を打ち切る
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) {
        Object start = request.getAttribute(START_ATTR);
        if (start == null) {
            return;
        }
        long ms = (System.nanoTime() - (Long) start) / 1_000_000;
        String target = (handler instanceof HandlerMethod hm)
                ? hm.getBeanType().getSimpleName() + "#" + hm.getMethod().getName()
                : handler.getClass().getSimpleName();
        log.info("[Interceptor] {} status={} {}ms", target, response.getStatus(), ms);
    }
}

登録は WebMvcConfigurer の addInterceptors で行います。

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.example.demo.web.TimingInterceptor;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TimingInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/health")
                .order(0);
    }
}

Interceptor は handler 引数から「どのクラスのどのメソッドか」を取り出せます。 HandlerMethod を使うと、メソッドに付いたアノテーションも調べられます。一方、コントローラーのメソッドに渡る引数(@RequestBody で変換されたオブジェクトなど)と戻り値は受け取れません。

3. AOP(@Aspect)

package com.example.demo.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimingAspect {

    private static final Logger log = LoggerFactory.getLogger(TimingAspect.class);

    @Around("within(com.example.demo.service..*)")
    public Object measure(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        try {
            return pjp.proceed(); // 本体のメソッドを実行
        } finally {
            long ms = (System.nanoTime() - start) / 1_000_000;
            log.info("[AOP] {} args={} {}ms",
                    pjp.getSignature().toShortString(), pjp.getArgs().length, ms);
        }
    }
}

@Aspect だけではコンポーネントスキャンの対象にならないため、@Component も付けます(Spring Framework リファレンス)。AOP は HTTP とは関係なく、サービスやリポジトリなど Spring の Bean のメソッドならどこにでも入れられます。 引数は pjp.getArgs()、戻り値は pjp.proceed() の結果として受け取れます。

3つのコードで取れる情報の違い

取れる情報 Filter Interceptor AOP
URL・HTTP メソッド・ヘッダー 取れる 取れる 引数で受け取れば可
レスポンスのステータス 取れる 取れる 対象外
呼ばれるクラスとメソッド 対象外 取れる 取れる
メソッドの引数と戻り値 対象外 対象外 取れる

5つの仕組みの比較表

@ControllerAdvice と Spring Security のフィルターも加えて比べます。表は2つに分けています。

管理する主体・絞り方・順序

仕組み 管理する主体 対象の絞り方 順序の指定
Filter Servlet コンテナ URL パターン、shouldNotFilter @Order、setOrder
Security のフィルター FilterChainProxy SecurityFilterChain の対象 addFilterBefore/addFilterAfter
HandlerInterceptor Spring MVC(DispatcherServlet) addPathPatterns/excludePathPatterns order(int)(既定 0)
@ControllerAdvice Spring MVC annotations・パッケージ・assignableTypes ローカルの後にグローバル
AOP(@Aspect) Spring コンテナ(プロキシ) ポイントカット式 @Order(小さいほど外側)

補足です。

  • Filter:Servlet の仕様の部品です。Spring Boot では Bean として宣言すると組み込みコンテナに登録されます。
  • Spring Security のフィルター:DelegatingFilterProxy が Servlet コンテナと Spring の橋渡しをし、FilterChainProxy が SecurityFilterChain に並んだフィルターを順に呼びます(Spring Security リファレンス)。
  • @ControllerAdvice:コントローラー内の @ExceptionHandler が先に適用され、@ControllerAdvice のものはその後です。@ModelAttribute と @InitBinder は逆で、グローバルが先です。
  • AOP:@Order の値が小さいアスペクトほど優先度が高く、入るときは先、出るときは後に動きます。

触れられる情報と例外の行き先

仕組み リクエスト本文 ハンドラー 引数・戻り値
Filter 触れられる(差し替えも可) わからない 触れられない
Security のフィルター 触れられる わからない 触れられない
HandlerInterceptor 差し替えは想定外 わかる 触れられない
@ControllerAdvice 書き込み前の本文に入れる 対象を絞れる 例外・モデルを扱う
AOP(@Aspect) 引数として受け取る メソッド情報がわかる 触れられる

HandlerInterceptor の Javadoc には、「Filter のほうが強力で、チェーンに渡すリクエストとレスポンスのオブジェクトを差し替えられる」とあります。リクエスト本文を読み直せるように包む、レスポンスを圧縮するといった処理は Filter の役割です。

投げた例外が @ControllerAdvice の @ExceptionHandler に届くかどうかも、選ぶときの大事な基準です。

例外を投げた場所 @ExceptionHandler 行き先
Filter 届かない コンテナのエラー処理(/error へのディスパッチ)
Security のフィルター 届かない ExceptionTranslationFilter が認証開始または拒否の応答に変換
HandlerInterceptor 届く DispatcherServlet の例外解決の対象
コントローラーに掛けた AOP 届く コントローラーが投げた例外と同じ扱い
サービスに掛けた AOP 呼び出し元次第 コントローラーまで伝われば届く

Spring Framework リファレンスは、@ExceptionHandler を呼び出す仕組み(HandlerExceptionResolver)の対象を「リクエストのマッピング中、またはリクエストハンドラー(@Controller など)から投げられた例外」と説明しています。Filter は DispatcherServlet の外側にあるため、この対象に入りません。解決されなかった例外はコンテナまで伝わり、コンテナがエラー用の URL(例:/error)へ ERROR ディスパッチを行います。

Filter の中でエラー応答を返したいときは、例外を投げずにその場でレスポンスを書きます。

package com.example.demo.web;

import java.io.IOException;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.web.filter.OncePerRequestFilter;

public class RequestIdRequiredFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        String requestId = request.getHeader("X-Request-Id");
        if (requestId == null || requestId.isBlank()) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"error\":\"X-Request-Id ヘッダーが必要です\"}");
            return; // 次へ渡さずに応答する
        }
        filterChain.doFilter(request, response);
    }
}

リクエストが通る順番

正常時

sequenceDiagram
    participant C as クライアント
    participant F as Filter
    participant S as Securityのフィルター
    participant D as DispatcherServlet
    participant I as Interceptor
    participant A as AOP
    participant H as コントローラー
    C->>F: リクエスト
    F->>S: chain.doFilter
    S->>D: 認証・認可を通過
    D->>I: preHandle
    I-->>D: true
    D->>A: ハンドラーを呼び出す
    A->>H: proceed
    H-->>A: 戻り値
    A-->>D: 戻り値
    D->>I: postHandle
    D->>I: afterCompletion
    D-->>S: 戻る
    S-->>F: 戻る
    F-->>C: レスポンス

入るときは外側から、出るときは内側から順に戻ります。図の AOP は「コントローラーにアスペクトを掛けた場合」で、サービスに掛けた場合はコントローラーの内側(サービスを呼ぶ手前)に入ります。Filter どうしの前後は @Order などの設定で決まり、図は自作の Filter を Spring Security より前に置いた場合です。

コントローラーで例外が起きたとき

sequenceDiagram
    participant C as クライアント
    participant F as Filter
    participant D as DispatcherServlet
    participant I as Interceptor
    participant H as コントローラー
    participant E as ControllerAdvice
    C->>F: リクエスト
    F->>D: chain.doFilter
    D->>I: preHandle
    I-->>D: true
    D->>H: ハンドラーを呼び出す
    H-->>D: 例外
    Note over D,I: postHandle は呼ばれない
    D->>E: ExceptionHandler で応答を作る
    E-->>D: エラー応答
    D->>I: afterCompletion
    D-->>F: 戻る
    F-->>C: エラー応答

@ExceptionHandler で応答に変換されると、Filter から見たときは通常どおり chain.doFilter から戻ってきます。前掲の TimingFilter が finally でログを出しているのは、正常時も例外時も同じ場所で計測を終えられるためです。

preHandle・postHandle・afterCompletion が呼ばれる条件

状況 preHandle postHandle afterCompletion
正常に完了 呼ばれる 呼ばれる 呼ばれる
preHandle が false を返した 呼ばれる 呼ばれない そのインターセプターでは呼ばれない
ハンドラーが例外を投げた 呼ばれる 呼ばれない 呼ばれる
@ResponseBody/ResponseEntity 呼ばれる 呼ばれるが応答は確定済み 呼ばれる

それぞれの根拠は HandlerInterceptor の Javadoc と Spring Framework リファレンス(7.0.9)です。

  • preHandle:HandlerMapping がハンドラーを決めた後、HandlerAdapter がハンドラーを呼ぶ前。false を返すと残りのチェーンとハンドラーは実行されません。
  • postHandle:ハンドラーが正常に実行された後、ビューの描画前。複数ある場合は登録の逆順です。
  • afterCompletion:ビューの描画後。ハンドラーの結果にかかわらず呼ばれますが、そのインターセプターの preHandle が正常に完了して true を返した場合だけです。こちらも逆順です。
  • @ResponseBody と ResponseEntity:レスポンスは postHandle より前に HandlerAdapter の中で書き込まれ確定します。postHandle でヘッダーを足しても間に合わないため、公式は ResponseBodyAdvice を実装して @ControllerAdvice の Bean として宣言する方法を案内しています。

後始末(計測の終了、リソースの解放)は afterCompletion に書くと、例外時にも動きます。

用途別の選び方の早見表

用途 選ぶ仕組み 理由
認証(ログイン状態の確認) Spring Security フィルターの並びの早い段階で判定できる
URL 単位の認可 Spring Security(authorizeHttpRequests) 公式が Interceptor より推奨
メソッド単位の認可 Spring Security(@PreAuthorize) Spring AOP の上に作られた仕組み
CORS CorsFilter または MVC の CORS 設定 Security 併用時は CorsFilter を前段に
全リクエストのアクセスログ Filter 静的ファイルやエラー応答も含めて拾える
リクエスト ID の採番 Filter 以降のすべての処理で使える
リクエスト本文の加工・包み直し Filter オブジェクトを差し替えられる
コントローラー別の処理時間 HandlerInterceptor ハンドラーの情報が取れる
アノテーションに応じた前処理 HandlerInterceptor HandlerMethod で判定できる
レスポンス本文への共通項目の追加 ResponseBodyAdvice 書き込み前に本文へ入れる
例外を共通の形式で返す @ControllerAdvice 例外の種類ごとに応答を決められる
サービス層のログ・計測 AOP 引数と戻り値が取れる
操作の記録(監査ログ) AOP 業務メソッド単位で記録できる
トランザクション @Transactional Spring が用意した AOP の仕組み

迷ったときは次の順で考えると決めやすくなります。

  1. HTTP そのものの話(ヘッダー、文字コード、本文の包み直し)なら Filter
  2. 認証・認可なら Spring Security
  3. 「どのコントローラーか」で動きを変えるなら HandlerInterceptor
  4. 例外をレスポンスに変えるなら @ControllerAdvice
  5. Web 以外(バッチやメッセージ受信)からも通る業務メソッドに掛けるなら AOP

認可を Interceptor から Spring Security に移す例

Spring Framework リファレンスの Interceptors のページには、次の注意書きがあります(日本語訳)。

インターセプターは、アノテーション付きコントローラーのパスマッチングと食い違う可能性があるため、セキュリティ層としては理想的ではありません。一般的には、Spring Security を使うか、Servlet フィルターチェーンに統合された同様の方法で、できるだけ早い段階で適用することを推奨します。

インターセプターの対象は addPathPatterns に書いたパターンで決まり、コントローラーのマッピングとは別に管理されます。2か所の書き方がずれると、意図した URL に掛からないことがあります。

移す前(Interceptor で管理者かどうかを判定)

package com.example.demo.web;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import org.springframework.web.servlet.HandlerInterceptor;

public class AdminOnlyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        Object role = (session != null) ? session.getAttribute("role") : null;
        if (!"ADMIN".equals(role)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);
            return false;
        }
        return true;
    }
}
package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.example.demo.web.AdminOnlyInterceptor;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AdminOnlyInterceptor())
                .addPathPatterns("/admin/**");
    }
}

移した後(SecurityFilterChain で URL 単位に指定)

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authorize) -> authorize
                .requestMatchers("/assets/**", "/signup").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            );
        // 認証方式(ログインの方法)の設定をここに加える

        return http.build();
    }
}

Spring Security リファレンスによると、認可を担当する AuthorizationFilter は既定で Spring Security のフィルターチェーンの最後に置かれ、その後に DispatcherServlet が動きます。コントローラーに届く前に判定が終わるので、Interceptor・AOP・コントローラーはすべて認可済みの前提で書けます。

メソッド単位で指定する場合

URL ではなく業務メソッドに対して権限を決めたいときは、メソッドセキュリティを使います。

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}
package com.example.demo.service;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

@Service
public class MemberAdminService {

    @PreAuthorize("hasRole('ADMIN')")
    public void deactivate(Long memberId) {
        // ADMIN ロールを持つ場合だけ実行される
    }
}

メソッドセキュリティは Spring AOP を使って作られており、拒否されたときは AccessDeniedException が投げられ、ExceptionTranslationFilter が 403 の応答に変換します(Spring Security リファレンス)。

自作の Filter を Security のチェーンに入れるとき

自作の Filter を Spring Security のフィルターの並びの中に置きたいときは、addFilterBefore/addFilterAfter で位置を指定します。前掲の RequestIdRequiredFilter をログアウト処理のフィルターより前に入れる例です。

package com.example.demo.config;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;

import com.example.demo.web.RequestIdRequiredFilter;

@Configuration
public class SecurityConfig {

    @Bean
    public RequestIdRequiredFilter requestIdRequiredFilter() {
        return new RequestIdRequiredFilter();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, RequestIdRequiredFilter filter) throws Exception {
        http
            .authorizeHttpRequests((authorize) -> authorize
                .anyRequest().authenticated()
            )
            .addFilterBefore(filter, LogoutFilter.class);
        return http.build();
    }

    /** Bean にした Filter を、組み込みコンテナには登録しない */
    @Bean
    public FilterRegistrationBean<RequestIdRequiredFilter> requestIdFilterRegistration(RequestIdRequiredFilter filter) {
        FilterRegistrationBean<RequestIdRequiredFilter> registration = new FilterRegistrationBean<>(filter);
        registration.setEnabled(false);
        return registration;
    }
}

Spring Security リファレンスには、Filter を @Component などで Bean にすると Spring Boot が組み込みコンテナにも自動登録するため、「コンテナから1回、Spring Security から1回」の計2回、違う順序で呼ばれることがあると書かれています。Bean にしたい場合は、上の3つ目の Bean のように FilterRegistrationBean の enabled を false にして、コンテナへの登録を止めます。

javax から jakarta への import 対応表

Spring Boot 3.0 の移行ガイドに「Jakarta EE は javax ではなく jakarta パッケージを使う」とあり、Spring Boot 4 は Jakarta EE 11・Servlet 6.1 を基準にしています。2.x 向けのサンプルを貼るときは、import を次のように読み替えます。

Boot 2.x まで Boot 3.x・4.x
javax.servlet.Filter jakarta.servlet.Filter
javax.servlet.FilterChain jakarta.servlet.FilterChain
javax.servlet.ServletException jakarta.servlet.ServletException
javax.servlet.ServletRequest jakarta.servlet.ServletRequest
javax.servlet.ServletResponse jakarta.servlet.ServletResponse
javax.servlet.http.HttpServletRequest jakarta.servlet.http.HttpServletRequest
javax.servlet.http.HttpServletResponse jakarta.servlet.http.HttpServletResponse
javax.servlet.http.HttpSession jakarta.servlet.http.HttpSession
javax.servlet.annotation.WebFilter jakarta.servlet.annotation.WebFilter

org.springframework.*(OncePerRequestFilter・HandlerInterceptor など)と org.aspectj.*(@Aspect・@Around など)のパッケージ名はそのままです。

版ごとの基準は次のとおりです。

項目 Spring Boot 3.0 Spring Boot 4.0
Java 17 以上 17 以上
Spring Framework 6.0 7.x
Jakarta EE 10(Servlet 6.0) 11(Servlet 6.1)
Web のスターター spring-boot-starter-web spring-boot-starter-webmvc
AOP のスターター spring-boot-starter-aop spring-boot-starter-aspectj

よくある質問

Q. Filter と Interceptor の両方で同じログを出す必要はありますか? A. 片方で足ります。静的ファイルやエラー応答も含めて全リクエストを記録したいなら Filter、コントローラー名とメソッド名を残したいなら Interceptor が合います。

Q. Filter で投げた例外を @ControllerAdvice で受け取れますか? A. 受け取れません。@ExceptionHandler の対象は DispatcherServlet の中で起きた例外です。Filter ではその場でレスポンスを書くか、判定そのものをコントローラーに近い側(Interceptor や AOP)に移します。

Q. Interceptor でリクエスト本文(JSON)を読めますか? A. 本文は一度読むと読み直せないストリームなので、Interceptor で読む設計は合いません。変換後のオブジェクトを見たいなら AOP でコントローラーの引数を受け取る、本文そのものを扱いたいなら Filter で包み直す、のどちらかにします。

Q. AOP はコントローラーにも掛けられますか? A. 掛けられます。コントローラーも Spring の Bean なので、ポイントカットの対象にできます。ただし HTTP のステータスやヘッダーは引数に無いかぎり見えないため、Web 寄りの情報が必要なら Interceptor のほうが書きやすくなります。

まとめ

  • Filter は DispatcherServlet の手前で HTTP のリクエスト・レスポンスを扱い、オブジェクトの差し替えもできる
  • HandlerInterceptor は DispatcherServlet の中で、どのハンドラーが呼ばれるかを知ったうえで前後に入る
  • AOP は Bean のメソッド呼び出しに入り、引数・戻り値・例外を扱える。Web 以外の入口からも効く
  • @ExceptionHandler に届くのは DispatcherServlet の中で起きた例外。Filter の例外は届かない
  • postHandle はハンドラーが正常に終わったときだけ、afterCompletion は preHandle が true を返していれば結果にかかわらず呼ばれる
  • 認証・認可は Spring Security に任せるのが公式の推奨。Interceptor のパス指定はコントローラーのマッピングと別管理になる
  • 2.x のサンプルは javax.servlet を jakarta.servlet に読み替える。Boot 4 ではスターター名も変わっている

参考・出典

確認日はいずれも 2026年10月7日です。

  • Spring Framework リファレンス(7.0.9)MVC Config Interceptors:https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config/interceptors.html
  • Spring Framework リファレンス(7.0.9)Interception:https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/handlermapping-interceptor.html
  • Spring Framework リファレンス(7.0.9)Exceptions:https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/exceptionhandlers.html
  • Spring Framework リファレンス(7.0.9)Controller Advice:https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-advice.html
  • Spring Framework リファレンス(7.0.9)Filters:https://docs.spring.io/spring-framework/reference/web/webmvc/filters.html
  • Spring Framework リファレンス(7.0.9)Declaring a Pointcut:https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/pointcuts.html
  • Spring Framework リファレンス(7.0.9)Declaring Advice:https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/advice.html
  • HandlerInterceptor Javadoc(7.0.9):https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/HandlerInterceptor.html
  • OncePerRequestFilter Javadoc(7.0.9):https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/OncePerRequestFilter.html
  • InterceptorRegistration Javadoc(7.0.9):https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/InterceptorRegistration.html
  • Spring Boot リファレンス(4.1.1)Servlet Web Applications:https://docs.spring.io/spring-boot/reference/web/servlet.html
  • Spring Security リファレンス(7.1.1)Architecture:https://docs.spring.io/spring-security/reference/servlet/architecture.html
  • Spring Security リファレンス(7.1.1)Authorize HttpServletRequests:https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html
  • Spring Security リファレンス(7.1.1)Method Security:https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html
  • Spring Boot 4.0 Migration Guide:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide
  • Spring Boot 3.0 Migration Guide:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Spring Filter・Interceptor・AOPの違い|比較表と実行順」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?