공통 프로세스 (Filter - Interceptor - AOP)

랏 뜨·2024년 12월 23일

🔎 Overview

  이전 포스팅에서 스프링 프레임워크의 역할 중 AOP가 있다고 이야기한 바 있다.
AOP를 사용해서 얻는 이점 정도만 정리했을 뿐, AOP가 무엇이고 왜, 어떻게 사용되는지는 일절 설명하지 않았다.

  하지만 이 AOP 또한 스프링 개발에서 매우 중요한 역할을 맡고 있다. 당연히 자주 쓰이기도 하고 말이다.
자주 쓰이지만 어떤건지, 어떻게 동작하는지 그 원리도 모르면서 관습적으로 사용하기보다, 확실히 이해하고 필요할 때 적재적소에 사용할 수 있어야 좋은 개발자가 될 수 있을 것이다.

  그런 의미에서 이번엔 AOP를 비롯해, 스프링에서 그와 비슷한 역할을 하는 3가지 도구에 대해서 알아보고자 한다.


💾 공통 프로세스

  • 비즈니스 개발 외에도 공통적으로 처리하는 게 좋은 기능들이 많이 있음
  • 로깅, 보안 등이 대표적인 예시
  • 이러한 공통된 부분은 따로 분리하여 관리하는 것이 좋음
  • 이러한 공통 업무를 프로그램 흐름에 따라, 단위별로 자동으로 처리할 수 있는 방법이 존재
  • Filter , Interceptor , AOP 가 그 주인공

https://goddaehee.tistory.com/154

각 공통 프로세스의 전체적인 흐름



  • FilterInterceptorServlet 단위에서 실행
  • AOP 는 메서드 앞에 Proxy 로 감싸져 실행
  • Filter -> Interceptor -> AOP 순서 및 역순으로 처리


🌊 전체적인 실행 흐름

1) 애플리케이션이 실행되면 서블릿 컨테이너가 구동되며, DispatcherServlet을 초기화

  • 이 과정에서 서블릿 설정과 관련된 메타데이터( web.xml or @Configuration ) 처리

2) 클라이언트의 요청이 들어오면, FilterdoFilter 실행

  • 요청/응답에 대한 전처리 작업 실행

3) 요청이 DispatcherServlet에 도달하면, preHandler가 실행

  • preHandlerInterceptor 의 일부이며, HandlerInterceptor 인터페이스에 정의된 메서드
  • Interceptor 내부에서 요청의 전처리 단계를 담당

4) DispatcherServlet 은 요청을 컨트롤러 매핑 하여 적절한 HandlerAdapter 를 통해 컨트롤러 호출

5) 컨트롤러, 서비스 등 각 계층 전체에서, 비즈니스 로직과 관련된 각 메서드 실행 전후AOP 를 통한 작업 처리

6) 컨트롤러 실행 후, postHandler가 실행

  • 마찬가지로 Interceptor 의 일부이며, Interceptor 내부에서 요청의 후처리 단계를 담당

7) 서블릿 종료 시 distroy 실행


1️⃣ Filter

  • 서블릿 컨테이너에서 동작하는 컴포넌트로, HTTP 요청/응답을 가로채고 전/후 처리 작업 수행
  • 전처리
    • 요청에 대한 작업 수행
    • ex) 인증, 로깅, 요청 변환
  • 후처리
    • 서블릿에서 생성된 응답에 대한 추가 작업 수행
    • ex) 응답 헤더 추가 및 변환
  • 요청이 스프링 컨텍스트에 도달하기 전에 실행


⏰ 실행 순서

1) 클라이언트가 서버에 HTTP 요청을 보냄
2) 서블릿 컨테이너가 요청을 처리하기 전에, 등록된 Filter Chain을 따라 Filter 실행
3) 요청은 각 Filter를 순차적으로 통과하고, 모두 통과하면 서블릿으로 전달됨

  • 이 때, Filter ChaindoFilter() 메서드가 호출되어 다음 필터 및 서블릿으로 요청 전달

4) 서블릿에서 응답을 생성한 후, 응답은 다시 Filter Chain을 거쳐 클라이언트로 반환


🔗 예시 코드

@WebFilter("*")
public class MyFilter implements Filter {
	@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    		throws IOException, ServletExcepton {
            
    	System.out.println("요청 처리 전 Filter");
        chain.doFilter(request, response)	// 다음 필터 및 서블릿으로 요청 전달
        System.out.println("요청 처리 후 Filter");
    }
}

2️⃣ Interceptor

  • 스프링 MVC에서 동작하며, 요청이 컨트롤러에 도달하기 전후, 응답 완료 후에 특정 작업 수행
  • 요청이 컨트롤러에 전달되기 직전에 실행되며, 컨트롤러 실행 이후 및 응답 완료 이후에도 작업 수행 가능


⏰ 실행 순서

1) 요청은 서블릿에서 처리된 후 스프링 MVC로 전달됨
2) HandlerInterceptor 가 등록된 순서대로 실행

  • preHandle : 컨트롤러 호출 실행
    • 전처리 (인증/인가 체크, 로깅)
    • boolean 값에 따라 요청 전달 유무 판단
  • postHandle : 컨트롤러가 요청을 처리하고 응답을 반환한 실행
    • 후처리
    • 뷰 렌더링 전에 실행
  • afterCompletion : 응답이 클라이언트로 반환되기 직전 실행
    • 모든 작업 완료 후 실행 (리소스 정리, 에러 처리)


🔗 예시 코드

public class MyInterceptor implements HandlerInterceptor {
	@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    		throws Exception {
    	System.out.println("컨트롤러 호출 전 Interceptor");
        return true;
    }
    
    @Override
    public boolean postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
    		ModelAndView modelAndView) throws Exception {
    	System.out.println("컨트롤러 호출 후 Interceptor");
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
    		Exception ex) throws Exception {
        System.out.println("모두 완료");
    }
}

3️⃣ AOP

  • Aspect-Oriented Programming : 관점 지향 프로그래밍
  • 스프링 컨테이너에서 동작하며, 특정 메서드 호출 시점에 공통 기능을 삽입하는 프로그래밍 패턴
  • 프록시 객체를 사용, 메서드 호출 전/후, 예외 발생 시점 등에 원하는 로직을 실행


⏰ 실행 순서

1) 요청이 스프링 컨테이너에 도달
2) 스프링 컨텍스트에서 프록시 객체를 통해 대상 객체에 접근
3) 프록시 객체JoinPoint 를 가로채고 Advice 실행

  • Aspect 에 정의된 Advice 실행
  • @Before : 메서드 호출 전 실행
  • @After : 메서드 호출 후 실행
  • @Around : 메서드 호출 전/후 모두 실행, 직접 실행하거나 실행을 건너뛸 수 있는 가장 강력한 Advice

4) 대상 객체의 메서드를 호출한 후 반환값 처리

  • JoinPointproceed() 메서드를 통해 실제 메서드 호출

5) 응답이 클라이언트로 전달


🔗 예시 코드

@Aspect
@Component
public class myAspect {
	@Before("execution(* com.example.service.*.*(..))")	// 타겟 메서드 실행 전 실행할 동작
    public void logBefore(JoinPoint joinPoint) {
    	// 실행되는 메서드의 이름을 반환
    	System.out.println("AOP 메서드 호출 전 : " + joinPoint.getSignature().getName());
    }
    
    @After("execution(* com.example.service.*.*(..))") // 타겟 메서드 실행 후 실행할 동작
    public void logAfter(JoinPoint joinPoint) {
    	System.out.println("AOP 메서드 호출 후 : " + joinPoint.getSignature().getName());
    }
    
    @Around("execution(* com.example.service.*.*(..))") // 타겟 메서드 전후로 실행
    public Object logAround(ProceedingJoinPoint joinPoint) {
    	System.out.println("AOP 메서드 호출 전 : " + joinPoint.getSignature().getName());
        
        Object result = joinPoint.proceed();	// 타겟 메서드 실행 결과 반환
        
        System.out.println("AOP 메서드 호출 후 : " + joinPoint.getSignature().getName());
        
        return result;	// 호출한 곳으로 타겟 메서드의 결과를 반환
    }
}

💡 포인트컷 execution(* com.example.service.*.*(..)) 해석

  • 첫 번째 * 위치
    • 메서드 반환 타입 (int , String , void ...)
    • 반환 타입으로 모든 것을 허용
  • 두 번째 * 위치
    • 클래스 이름
    • example.service 패키지 아래의 모든 클래스 대상
  • 세 번째 * 위치
    • 메서드 이름
    • 클래스 내 모든 메서드 대상
  • (..)
    • 파라미터 개수와 타입 지정
    • .. 은 모든 타입과 개수 허용
    • (String) : String 타입 하나의 파라미터를 받는 메서드만 매칭
    • (*, int) : 두 개의 파라미터 중 첫 번째는 상관 없고, 두 번째는 int 타입인 메서드만 매칭


🤔 logAround() 메서드 내에서 logBefore , logAfter 메서드와 동일한 역할을 수행하는 부분이 있는데, 그대로 사용해도 될까?

  • 부적절
  • 이미 @Before@After 로 전/후 처리를 해뒀음
  • logAround() 에서 이를 호출하면, 전/후 처리를 한 번씩 추가로 진행
  • Advice 는 다른 Advice 를 신경쓰지 말고, 자신의 역할에만 충실

📑 정리

Filter, Interceptor, AOP의 비교

특징FilterInterceptorAOP
적용 범위서블릿 수준스프링 MVC 수준스프링 컨테이너 전역 (핵심 로직)
적용 시점요청 전후컨트롤러 전후메서드 전후, 예외 처리, 객체 초기화 등
사용 목적인증, 요청/응답 처리인증, 데이터 가공, MVC 로직 처리트랜잭션, 로깅, 공통 로직 처리
설정 방식서블릿 컨테이너 설정WebMvcConfigurer 설정@Aspect 및 Pointcut 설정

참고) OpenAI. (2024).ChatGPT(4o)[Large language model].https://chatgpt.com/
https://goddaehee.tistory.com/154

profile
기록

0개의 댓글