[강의] Spring AOP, Interceptor, Filter

Jerry·2025년 8월 19일

Spring AOP, Interceptor, Filter

Spring Filter류 종류

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

Spring Filter류 종류 비교

구분FilterInterceptorAOP
동작 위치서블릿 앞단 (DispatcherServlet 전에 실행)컨트롤러 전후 (DispatcherServlet 이후)스프링 빈 메서드 호출 시 (프록시 기반)
대상모든 요청/응답 (정적 리소스 포함)MVC 핸들러(Controller)에 한정Spring Bean 메서드 (Service/Repository 등)
주요 용도인코딩, CORS, 인증 필터, 로깅인증/권한 체크, 로깅, 모델 처리트랜잭션, 로깅, 보안 어노테이션, 캐싱
설정 방법Filter 구현 후 등록HandlerInterceptor 구현 후 addInterceptors@Aspect + 포인트컷/어드바이스
특징전역적이고 가장 앞단 제어MVC 맥락 정보 활용 가능비즈니스 로직 침투 없이 공통 로직 분리

Spring AOP 개요

Spring AOP 개념

관점 지향 프로그래밍(Aspect Oriented Programming)의 약자로, 일반적으로 사용하는 클래스(Service, DAO 등)에서 중복되는 공통 코드 부분(commit, rollback, log 처리)을 AOP라는 별도의 영역으로 분리하고 코드가 실행되기 전이나 후의 시점에 공통 영역 코드를 실행할 수 있는 구성이 가능하도록 도와주는 기술
→ 이를 통해 소스 코드 중복을 줄이고, 비즈니스 로직과 공통 코드를 분리하여 구성 가능

Spring AOP 동작 구조

  • 공통되는 부분을 따로 빼내어 작성하는 클래스를 Advice라고 하며, Advice를 실행하는 시점을 Joinpoint, 그리고 그 시점에 공통 코드를 끼워 넣는 작업을 Weaving이라고 함

Spring AOP 용어

Aspect

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

Spring AOP 핵심 용어

용어설명
Aspect여러 객체에 공통으로 적용되는 기능을 분리하여 작성한 클래스
JoinPoint객체(인스턴스) 생성 시점, 메서드 호출 시점, 예외 발생 지점 등 특정 작업이 시작되는 시점
AdviceJoinPoint에 삽입되어 동작될 코드(메서드)
Before AdviceJoinPoint 앞에서 실행
Around AdviceJoinPoint 앞뒤에서 실행
After AdviceJoinPoint 호출이 리턴되기 직전에 실행
After Returning AdviceJoinPoint 메서드 호출이 정상적으로 종료된 후에 실행
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 ObjectAdvice가 삽입될 원래 대상 객체 (프록시 뒤에 있는 실제 객체)

AOP 특징 및 구현 방식

AOP 코드 구현 구조

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 AOP 특징

Spring은 Proxy 기반 AOP를 지원

Spring은 대상 객체(Target Object)에 대한 프록시를 만들어 제공하며 타겟을 감싸는 프록시는 서버 Runtime 시에 생성됨
이렇게 생성된 프록시는 대상 객체를 호출할 때 먼저 호출되어 어드바이스의 로직을 처리 후 대상 객체 호출

Proxy는 대상 객체의 호출을 가로챈다(Intercept)

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

Spring AOP는 메서드 조인 포인트만 지원

Spring은 동적 Proxy를 기반으로 AOP를 구현하기 때문에 메서드 조인 포인트만 지원
즉, 핵심 기능(대상 객체)의 메서드가 호출되는 런타임 시점에만 부가기능(Advice) 적용 가능
하지만 AspectJ와 같은 고급 AOP 프레임워크를 사용하면 객체의 생성, 필드 값의 조회와 조작, static 메서드 호출 및 초기화 등의 다양한 작업에 부가기능 적용 가능

@Pointcut("execution(* com.codeit.test.Service.method(..))")
public void serviceMethodPointcut() {}

Spring AOP 관련 어노테이션

구분설명
@Aspect해당 클래스를 AOP에서 사용하는 관점(Aspect) 클래스로 선언
@Pointcut어드바이스 적용 지점(메서드 실행 위치, 패키지, 어노테이션 등)을 정의
@Before("pointcut")타겟 객체 메서드 실행 이전에 호출되는 어드바이스
JoinPoint를 통해 파라미터 정보 참조 가능
@Around("pointcut")타겟 객체 메서드 호출 전후에 모두 실행될 코드를 구현하는 어드바이스
@After("pointcut")타겟 객체 메서드가 정상 종료/예외 발생 여부와 상관없이 실행되는 어드바이스
→ 반환 값을 받을 수 없음
@AfterReturning(pointcut="", returning="")타겟 객체 메서드가 정상적으로 실행 완료된 후 호출되는 어드바이스
returning 속성에 지정한 변수 이름으로 리턴 값 참조 가능
@AfterThrowing(pointcut="", throwing="")타겟 객체 메서드에서 예외 발생 시 호출되는 어드바이스
throwing 속성에 지정한 변수 이름으로 발생한 예외 참조 가능

Pointcut 어노테이션 표현식

@Pointcut("execution(* com.codeit.test.Service.method(..))")
				// 리턴타입		패키지명+클래스명	  메서드 + 인자형태

Ponitcut은 advice를 동작 시킬 대상을 지정하는 문장으로 패키지, 클래스, 메서드 꼴을 지정 가능

구분예시설명
리턴 타입*모든 리턴 타입 허용
void / int / longvoid, int, long 타입 리턴 허용
!voidvoid가 아닌 리턴 타입 지정
패키지명com.codeit.testcom.codeit.test 패키지에 해당되는 클래스만 선택
com.codeit.test..com.codeit.test로 시작하는 모든 하위 패키지 포함
com.codeit.test..*servicecom.codeit.test로 시작하고, 이름이 service로 끝나는 패키지 선택
클래스명BoardServiceImplBoardServiceImpl 클래스만 선택
*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개의 인자를 가지는 메서드

JoinPoint의 개념

객체(인스턴스) 생성 시점, 메서드 호출 시점, 예외 발생 지점 등 특정 작업이 시작되는 시점
실제 호출 되는 메서드의 정보를 반환하는 객체로 활용됨

@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()대상 객체 메서드에 대한 간단한 정보 문자열 출력

Advice 작성하기

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;
    }
}

Interceptor

Interceptor의 개념

Spring에서 제공하는 필터 메커니즘으로 URL을 기반으로 사용자가 작성한 Handler를 Controller 호출한 시점 전(Pre)과 후(Post)에 호출 시켜주는 기능, 필터와 개념과 사용법이 유사하지만, 스프링 고유 처리를 지원하고 Bean을 통해 관리 되므로 Spring에서는 필터보단 인터셉터로 구현하는 것을 권장함
인터셉터는 주로 권한/인증 관리(로그인 기능)를 개발자가 직접 개발할 때 활용된다.
→ Spring security를 통해 권한/인증 관리 기능을 활용할 수 있으나 둘 다 알고 있길 권장 (둘 다 사용)

Interceptor 설정 방법

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 구현 방법

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(">> 요청 완료 후 처리");
    }
}

Interceptor 종류

HandlerInterceptorAdapter의 핸들러의 종류

메서드실행 시점설명
preHandle컨트롤러 실행 이전컨트롤러 호출 전에 실행되는 핸들러. 인증·권한 체크, 로깅, 요청 차단 등에 활용
postHandle컨트롤러 실행 이후, View 렌더링 전컨트롤러가 정상 실행된 후, 뷰가 렌더링되기 전에 실행. ModelAndView 조작 가능
afterCompletion뷰 렌더링까지 완료된 직후요청 처리 완료 후 실행. 예외 여부와 상관없이 호출되며, 자원 해제·최종 로깅 등에 사용
afterConcurrentHandlingStarted비동기 요청(@ResponseBody, Callable, DeferredResult) 시ViewResolver를 사용하지 않고 직접 응답을 처리할 때 호출. 비동기 요청의 별도 처리 시점 제공

Servlet Filter

Servlet Filter의 개념

서블릿 필터는 서블릿 컨테이너에서 제공하는 기능으로, 클라이언트의 요청과 서버의 응답을 가로채 전•후 처리할 수 있는 컴포넌트이다. 주로 인코딩 설정, 인증•인가, 로깅, 보안 검사 등 공통 기능을 처리하는 데 사용된다. DispatcherServlet 앞단에서 동작하기 때문에 모든 요청과 응답에 대해 전역적으로 적용 가능하다.

Servlet Filter 동작 구조

Servlet Filter의 등록

서블릿 필터는 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;
	}
}

Servlet Filter 구현

서블릿 필터는 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 종료");
	}
}

Servlet Filter 인터페이스 설명

  • init(FilterConfig config)
    • 웹 컨테이너가 필터를 호출할 경우 해당 메서드가 호출되어 필터 객체를 생성하며 초기화
    • 매개변수 FilterConfig는 web.xml에있는 <filter>정보를가지고 있음
  • doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
    • 필터가 수행될 때 구동하는 메서드로 요청 객체와 응답 객체를 사용해 일련의 작업을 수행한 뒤 chain을 통해 가공된 값을 목적지로 전송
  • destroy()
    • 역할이 끝난 필터는 웹 컨테이너에 의해 해당 메서드를 호출하고 소멸

To Study

profile
Backend engineer

0개의 댓글