Filter: Servlet 기반으로 동작, URL 기반으로 가장 강력한 필터를 수행함 (모든 요청과 응답)
Interceptor: 스프링 기반으로 동작, URL 기반으로 Handler를 지정하여 필터 역할을 수행
AOP: 스프링 기반으로 동작, 공통 코드를 분리하는 목적, Package, Class, method 기반으로 필터 수행

| 구분 | Filter | Interceptor | AOP |
|---|---|---|---|
| 동작 위치 | 서블릿 앞단 (DispatcherServlet 전에 실행) | 컨트롤러 전후 (DispatcherServlet 이후) | 스프링 빈 메서드 호출 시 (프록시 기반) |
| 대상 | 모든 요청/응답 (정적 리소스 포함) | MVC 핸들러(Controller)에 한정 | Spring Bean 메서드 (Service/Repository 등) |
| 주요 용도 | 인코딩, CORS, 인증 필터, 로깅 | 인증/권한 체크, 로깅, 모델 처리 | 트랜잭션, 로깅, 보안 어노테이션, 캐싱 |
| 설정 방법 | Filter 구현 후 등록 | HandlerInterceptor 구현 후 addInterceptors | @Aspect + 포인트컷/어드바이스 |
| 특징 | 전역적이고 가장 앞단 제어 | MVC 맥락 정보 활용 가능 | 비즈니스 로직 침투 없이 공통 로직 분리 |
관점 지향 프로그래밍(Aspect Oriented Programming)의 약자로, 일반적으로 사용하는 클래스(Service, DAO 등)에서 중복되는 공통 코드 부분(commit, rollback, log 처리)을 AOP라는 별도의 영역으로 분리하고 코드가 실행되기 전이나 후의 시점에 공통 영역 코드를 실행할 수 있는 구성이 가능하도록 도와주는 기술
→ 이를 통해 소스 코드 중복을 줄이고, 비즈니스 로직과 공통 코드를 분리하여 구성 가능


부가 기능을 정의한 코드인 Advice와 Advice를 어디에 적용할지 결정하는 PointCut을 합친 개념
AOP 개념을 적용하면 핵심 기능 코드 사이에 끼어있는 부가 기능을 독립적인 요소로 구분할 수 있고 구분된 부가기능 Aspect는 런타임 시 필요한 위치에 동적으로 참여 가능

| 용어 | 설명 |
|---|---|
| Aspect | 여러 객체에 공통으로 적용되는 기능을 분리하여 작성한 클래스 |
| JoinPoint | 객체(인스턴스) 생성 시점, 메서드 호출 시점, 예외 발생 지점 등 특정 작업이 시작되는 시점 |
| Advice | JoinPoint에 삽입되어 동작될 코드(메서드) |
| Before Advice | JoinPoint 앞에서 실행 |
| Around Advice | JoinPoint 앞뒤에서 실행 |
| After Advice | JoinPoint 호출이 리턴되기 직전에 실행 |
| After Returning Advice | JoinPoint 메서드 호출이 정상적으로 종료된 후에 실행 |
| After Throwing Advice | 예외가 발생했을 때 실행 |
| 용어 | 설명 |
|---|---|
| PointCut | 조인 포인트(JoinPoint)의 부분 집합으로, 실제 Advice가 적용되는 지점 |
| Introduction | 정적인 방식의 AOP 기술. 작성한 Advice(공통 코드)를 핵심 로직 코드에 삽입 |
| Weaving (위빙) | 핵심 로직(Target Object)과 부가 기능(Advice, Aspect)을 연결하는 과정 |
| └ 컴파일 시 위빙 | 컴파일 단계에서 AOP가 적용된 새로운 클래스 파일 생성 (AspectJ 방식) |
| └ 클래스 로딩 시 위빙 | JVM이 클래스를 로딩할 때 바이트코드를 수정하여 AOP 적용 |
| └ 런타임 시 위빙 | 클래스 자체를 바꾸지 않고, 프록시(Proxy)를 생성하여 경유 (Spring AOP 방식) |
| Proxy | 대상 객체(Target Object)에 Advice가 적용된 후 대리 역할을 하는 객체 |
| Target Object | Advice가 삽입될 원래 대상 객체 (프록시 뒤에 있는 실제 객체) |

Pointcut: 적용할 메서드 지점을 지정하는 표현식
Advice: 지정된 시점(Before, After 등)에 실행되는 부가 로직
Aspect: Pointcut과 Advice를 묶어 관리하는 모듈
@Aspect
@Component
public class LoggingAspect {
// Pointcut: Service.method(..) 실행 지점
@Pointcut("execution(* com.codeit.aop.Service.method(..))")
public void serviceMethodPointcut() {}
// Before Advice: 실행 전에 동작
@Before("serviceMethodPointcut()")
public void beforeServiceMethod() {
System.out.println(">> Service.method(..) 실행 전에 로그 출력");
}
// AfterReturning Advice: 정상 종료 후 동작
@AfterReturning("serviceMethodPointcut()")
public void afterServiceMethod() {
System.out.println("<< Service.method(..) 실행 후 로그 출력");
}
}
Spring은 대상 객체(Target Object)에 대한 프록시를 만들어 제공하며 타겟을 감싸는 프록시는 서버 Runtime 시에 생성됨
이렇게 생성된 프록시는 대상 객체를 호출할 때 먼저 호출되어 어드바이스의 로직을 처리 후 대상 객체 호출

Proxy는 그 역할에 따라 대상 객체에 대한 호출을 가로챈 다음 Advice의 부가 기능 로직을 수행하고 난 후에 타겟의 핵심 기능 로직을 호출하거나(전처리 어드바이스), 타겟의 핵심 기능 로직 메서드를 호출한 후에 Advice의 부가 기능 수행(후처리 어드바이스)

Spring은 동적 Proxy를 기반으로 AOP를 구현하기 때문에 메서드 조인 포인트만 지원
즉, 핵심 기능(대상 객체)의 메서드가 호출되는 런타임 시점에만 부가기능(Advice) 적용 가능
하지만 AspectJ와 같은 고급 AOP 프레임워크를 사용하면 객체의 생성, 필드 값의 조회와 조작, static 메서드 호출 및 초기화 등의 다양한 작업에 부가기능 적용 가능
@Pointcut("execution(* com.codeit.test.Service.method(..))")
public void serviceMethodPointcut() {}
| 구분 | 설명 |
|---|---|
| @Aspect | 해당 클래스를 AOP에서 사용하는 관점(Aspect) 클래스로 선언 |
| @Pointcut | 어드바이스 적용 지점(메서드 실행 위치, 패키지, 어노테이션 등)을 정의 |
| @Before("pointcut") | 타겟 객체 메서드 실행 이전에 호출되는 어드바이스 → JoinPoint를 통해 파라미터 정보 참조 가능 |
| @Around("pointcut") | 타겟 객체 메서드 호출 전후에 모두 실행될 코드를 구현하는 어드바이스 |
| @After("pointcut") | 타겟 객체 메서드가 정상 종료/예외 발생 여부와 상관없이 실행되는 어드바이스 → 반환 값을 받을 수 없음 |
| @AfterReturning(pointcut="", returning="") | 타겟 객체 메서드가 정상적으로 실행 완료된 후 호출되는 어드바이스 → returning 속성에 지정한 변수 이름으로 리턴 값 참조 가능 |
| @AfterThrowing(pointcut="", throwing="") | 타겟 객체 메서드에서 예외 발생 시 호출되는 어드바이스 → throwing 속성에 지정한 변수 이름으로 발생한 예외 참조 가능 |
@Pointcut("execution(* com.codeit.test.Service.method(..))")
// 리턴타입 패키지명+클래스명 메서드 + 인자형태
Ponitcut은 advice를 동작 시킬 대상을 지정하는 문장으로 패키지, 클래스, 메서드 꼴을 지정 가능
| 구분 | 예시 | 설명 |
|---|---|---|
| 리턴 타입 | * | 모든 리턴 타입 허용 |
void / int / long | void, int, long 타입 리턴 허용 | |
!void | void가 아닌 리턴 타입 지정 | |
| 패키지명 | com.codeit.test | com.codeit.test 패키지에 해당되는 클래스만 선택 |
com.codeit.test.. | com.codeit.test로 시작하는 모든 하위 패키지 포함 | |
com.codeit.test..*service | com.codeit.test로 시작하고, 이름이 service로 끝나는 패키지 선택 | |
| 클래스명 | BoardServiceImpl | BoardServiceImpl 클래스만 선택 |
*Impl | 이름이 Impl로 끝나는 클래스 선택 | |
BoardService+ | BoardService를 상속(구현)한 모든 자식 클래스 선택 |
| 구분 | 예시 | 설명 |
|---|---|---|
| 메서드 이름 | *(..) | 모든 메서드 지정 |
get*(..) | get으로 시작하는 메서드 지정 | |
| 매개변수(인자) | (..) | 모든 형태의 인자 허용 |
(*) | 인자 1개인 메서드 | |
(*, *) | 인자 2개인 메서드 | |
(com.codeit.vo.Member) | Member 객체를 1개의 인자로 받는 메서드 | |
(!com.codeit.vo.Member) | Member가 아닌 타입을 인자로 받는 메서드 | |
(Integer, ..) | 첫 번째 매개변수가 Integer이고, 그 뒤로 0개 이상 인자를 가지는 메서드 | |
(Integer, *) | 첫 번째 매개변수가 Integer이고, 추가로 정확히 1개의 인자를 가지는 메서드 |
객체(인스턴스) 생성 시점, 메서드 호출 시점, 예외 발생 지점 등 특정 작업이 시작되는 시점
실제 호출 되는 메서드의 정보를 반환하는 객체로 활용됨
@Before("allPointCut()")
public void printBeforeLog(JoinPoint jp) {
System.out.println("@Before : " + jp.getSignature().getName() + "() 호출됨");
System.out.println("@Before arg : " + Arrays.toString(jp.getArgs()));
}
| 메서드 | 설명 |
|---|---|
| getArgs() | 현재 호출된 메서드의 매개변수 배열(Object[]) 반환 |
| getThis() | 현재 사용 중인 프록시 객체(스프링 AOP가 만들어둔 대리 객체) 반환 |
| getTarget() | 실제 호출 대상인 원본 객체(Target Object) 반환 |
| getSignature() | 호출된 메서드의 시그니처 정보(메서드명, 리턴 타입, 매개변수 타입 등) 반환 |
| toString() | 대상 객체 메서드에 대한 간단한 정보 문자열 출력 |
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Aspect
@Component
public class AroundLog {
@Around("execution(* com.codeit..*Service.*(..))") // Service 계층의 모든 메서드
public Object aroundLog(ProceedingJoinPoint pp) throws Throwable {
String methodName = pp.getSignature().toShortString();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = null;
try {
result = pp.proceed(); // 실제 대상 메서드 실행
} finally {
stopWatch.stop();
System.out.println(methodName + " 수행 시간: " +
stopWatch.getTotalTimeMillis() + " ms");
}
return result;
}
}
Spring에서 제공하는 필터 메커니즘으로 URL을 기반으로 사용자가 작성한 Handler를 Controller 호출한 시점 전(Pre)과 후(Post)에 호출 시켜주는 기능, 필터와 개념과 사용법이 유사하지만, 스프링 고유 처리를 지원하고 Bean을 통해 관리 되므로 Spring에서는 필터보단 인터셉터로 구현하는 것을 권장함
인터셉터는 주로 권한/인증 관리(로그인 기능)를 개발자가 직접 개발할 때 활용된다.
→ Spring security를 통해 권한/인증 관리 기능을 활용할 수 있으나 둘 다 알고 있길 권장 (둘 다 사용)

WebMvcConfigurer를 구현한 설정 클래스에서 addInterceptors() 메서드를 오버라이드해 Interceptor를 등록한다. 이때 InterceptorRegistry를 사용해 적용할 URL 패턴과 제외할 경로를 지정할 수 있다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor())
.addPathPatterns("/api/**") // /api/** 경로에 적용
.excludePathPatterns("/api/auth/**"); // /api/auth/** 경로는 제외
}
}
Interceptor는 HandlerInterceptor 인터페이스를 구현하여 preHandle, postHandle, afterCompletion 메서드를 오버라이드해 작성한다. 이를 통해 요청 전 처리, 컨트롤러 실행 후 처리, 요청 완료 후 처리를 원하는 로직으로 구현할 수 있다.
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
log.info(">> 요청 URI: {}", request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
log.info(">> 컨트롤러 실행 후 처리");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
if (ex != null) {
log.error(">> 요청 중 예외 발생: {}", ex.getMessage(), ex);
}
log.info(">> 요청 완료 후 처리");
}
}
| 메서드 | 실행 시점 | 설명 |
|---|---|---|
| preHandle | 컨트롤러 실행 이전 | 컨트롤러 호출 전에 실행되는 핸들러. 인증·권한 체크, 로깅, 요청 차단 등에 활용 |
| postHandle | 컨트롤러 실행 이후, View 렌더링 전 | 컨트롤러가 정상 실행된 후, 뷰가 렌더링되기 전에 실행. ModelAndView 조작 가능 |
| afterCompletion | 뷰 렌더링까지 완료된 직후 | 요청 처리 완료 후 실행. 예외 여부와 상관없이 호출되며, 자원 해제·최종 로깅 등에 사용 |
| afterConcurrentHandlingStarted | 비동기 요청(@ResponseBody, Callable, DeferredResult) 시 | ViewResolver를 사용하지 않고 직접 응답을 처리할 때 호출. 비동기 요청의 별도 처리 시점 제공 |
서블릿 필터는 서블릿 컨테이너에서 제공하는 기능으로, 클라이언트의 요청과 서버의 응답을 가로채 전•후 처리할 수 있는 컴포넌트이다. 주로 인코딩 설정, 인증•인가, 로깅, 보안 검사 등 공통 기능을 처리하는 데 사용된다. DispatcherServlet 앞단에서 동작하기 때문에 모든 요청과 응답에 대해 전역적으로 적용 가능하다.


서블릿 필터는 Filter 인터페이스를 구현한 클래스를 작성한 뒤, Bean으로 등록하거나 FilterRegistrationBean을 통해 URL 패턴과 순서를 설정한다. 이후 DispatcherServlet 앞단에서 요청과 응답을 가로채어 지정된 로직을 수행한다.
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<LoggingFilter> loggingFilter() {
FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new LoggingFilter());
registrationBean.addUrlPatterns("/api/*"); // 적용할 URL 패턴
registrationBean.setOrder(1); // 필터 실행 순서 (낮을수록 먼저 실행)
return registrationBean;
}
}
서블릿 필터는 Filter 인터페이스를 구현하고 doFilter() 메서드에서 요청·응답에 대한 전처리와 후처리 로직을 작성한다. 필터 체인을 통해 chain.doFilter(request, response)를 호출해야 다음 필터나 서블릿으로 요청이 전달된다.
public class LoggingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(">> LoggingFilter 초기화");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println(">> 요청 들어옴: " + request.getRemoteAddr());
chain.doFilter(request, response); // 다음 필터/서블릿으로 전달
System.out.println("<< 응답 완료");
}
@Override
public void destroy() {
System.out.println(">> LoggingFilter 종료");
}
}