Spring

[Spring] Filter, Interceptor, AOP 비교

hail2y 2024. 9. 8. 14:56

Filter, Interceptor, AOP는 모두 어떤 작업을 수행하기 전 또는 후에 해야 할 작업을 미리 정의해 둔 것이다. 이 전처리, 후처리의 작업이 각 메서드에서 반복적으로 사용된다면 중복코드가 많이 발생하는데, 그 공통 코드를 분리하고자 하는 목적에서 사용한다. 예를 들어 어떤 작업이 수행하는 데 걸리는 시간을 구한다고 하면, 해당 작업의 수행 전 현재 시간을 저장해 두었다가 수행 후 현재 시간에서 빼면 된다. 이때 수행 전 시간을 구하는 과정, 수행 후 시간을 구하는 과정이 각각 전처리, 후처리 작업이 된다.

 

https://okky.kr/questions/1346808 - selfless 님 답변 중

1. Filter

  • 서블릿 초기화 시 init(), 처리 시 doFilter(), 서블릿 종료 시 destroy() 
  • Filter는 전처리, 후처리 작업을 모두 doFilter()에서 처리한다. 
  • ServletContext에 있기 때문에 서블릿에서만 사용 가능하다. 
import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

// 필터를 적용할 요청의 패턴 지정 - 모든 요청에 필터를 적용.
@WebFilter(urlPatterns="/*")
public class PerformanceFilter implements Filter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		// 초기화 작업
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// 1. 전처리 작업
		long startTime = System.currentTimeMillis();

		// 2. 서블릿 또는 다음 필터를 호출
		chain.doFilter(request, response); 
		
		// 3. 후처리 작업
		HttpServletRequest req = (HttpServletRequest) request;
		String referer = req.getHeader("referer"); // 어디서 요청했는지
		String method = req.getMethod();
		System.out.println("[" + referer + "] -> " + method + "[" + req.getRequestURI() + "]");
		System.out.print("["+((HttpServletRequest)request).getRequestURI()+"]");
		System.out.println(" 소요시간="+(System.currentTimeMillis()-startTime)+"ms");
	}

	@Override
	public void destroy() {
		// 정리 작업
	}
}

2. Interceptor

  • 중간에서 가로챈다
  • Filter의 발전된 형태
  • 전처리, 후처리 시 작업을 각각의 메서드로 분리하였다.
  • @Configuration 설정파일에 Interceptor를 등록해야 사용할 수 있다.
  • WebApplicationContext(Spring Context)에 있어 Bean에 접근할 수 있다. 
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
public class PerformanceInterceptor implements HandlerInterceptor { // SRP 원칙
//    long startTime; // iv, 싱글톤(하나의 객체)이라서 여러 쓰레드가 하나의 객체를 공유

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 전처리 작업
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);

        // handler - 요청과 연결된 컨트롤러의 메서드
        HandlerMethod method =(HandlerMethod)handler;
        System.out.println("method.getMethod() = " + method.getMethod() + "-- Interceptor 전"); // URL과 연결된 메서드
        System.out.println("method.getBean() = " + method.getBean() + "-- Interceptor 전"); // 메서드가 포함된 컨트롤러

        // return true이면 다음 인터셉터나 컨트롤러를 호출, false면 호출 안 함
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 2. 후처리 작업
        long endTime = System.currentTimeMillis();
        long startTime = (long)request.getAttribute("startTime");

        System.out.println(("["+((HttpServletRequest)request).getRequestURI() + "] -- Interceptor 후"));
        System.out.println("[소요시간:" + (endTime - startTime) + "ms] -- Interceptor 후");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

 

@Configuration 붙인 설정 파일에 Interceptor를 등록해야 사용할 수 있다. 

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

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new PerformanceInterceptor())
                .addPathPatterns("/**") // 인터셉터를 적용할 대상
                .excludePathPatterns("/css/**", "/js/**"); // 인터셉터 적용 제외 대상
    }
}

3. AOP 

  • 중복을 제거하기 위해 공통 코드를 분리한다.
  • 실행 중에 코드를 자동 추가한다.
  • 부가 기능(Advice)을 동적으로 추가해 주는 기술
    - 부가 기능인 Advice를 Target의 pointcut과 연결된 곳에 join 되어 Proxy 객체를 만들고 이 과정은 weaving이라 한다.
  • before + (핵심 기능)  + after     // around == before + after
  • 로깅, 권한 체크, 트랜잭션, APM
  • CGLIB이 클래스 파일의 컴파일되어 있는 곳에 추가한다.
  • 서블릿 상관없이 처리할 수 있다.

사용 방법 (w.chatGPT)

  • Spring AOP 라이브러리 추가: spring-aspects
  • AOP 기능 활성화 @EnableAspectJAutoProxy
  • AOP를 적용하는 클래스에 @Aspect 추가 (없으면 @Around 등 인식 못 함)
  • AOP 클래스가 Spring 빈으로 등록되어야 함 (@Component, @Bean)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect
@Component
public class AroundAdvice {

    @Around("execution(* com.fastcampus.ch2.*.*(..))")
    public Object methodCallLog(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("<<[start] "
            + pjp.getSignature() + Arrays.deepToString(pjp.getArgs()) + "-- AOP 전");

        Object result = pjp.proceed();

        System.out.println("result = " + result + "-- AOP 후");
        System.out.println("[end]>> " + (System.currentTimeMillis() - start) + "ms -- AOP 후");
        return result;
    }
}

 

cf. CGLIB(Code Generator Library) 코드 생성 라이브러리

- 클래스의 바이트 코드를 조작하여 프록시 객체를 생성해 주는 라이브러리

- 소스 레벨이 아닌 클래스 기반을 조작

https://okky.kr/questions/1346808 - selfless 님 답변 중

 

 

실제 실행 화면

 

위 실행 화면은 HomeController.main()을 호출했을 때의 모습이다. 처음에 AOP가 가장 먼저 실행되는 것처럼 보이는데, 자세히 보면 doFilter() 메서드에 적용된 모습이고, 이는 chatGPT에 의하면  AOP가 필터 내부의 메서드를 감쌀 경우 AOP가 먼저 실행될 수 있다고 한다. 아래를 보면 어떤 요청 때 실행되게 할 지 패턴을 적는 곳에 나는 모든 클래스의 모든 메서드로 지정해 놓았기 때문에 실행되었다. 그렇기 때문에 마지막 두 문장도 빼 놓고 생각해야 한다.

@Around("execution(* com.fastcampus.ch2.*.*(..))")

 

실행 순서는 차례로 Filter, (DispatchServlet), Interceptor, AOP, Controller 순이다. 요청이 들어올 때 이런 순서로 실행된다는 이야기고, 핵심 메서드가 실행을 마친 뒤 후처리하는 순서는 정확히 반대 방향이다.

 

 

https://okky.kr/questions/1346808

 

스프링 Filter와 interceptor가 현업에서 어떻게 쓰이는지 궁금합니다. | OKKY Q&A

안녕하세요,최근에 본 면접을 복기하면서 필터와 인터셉터 (+AOP)의 차이를 묻는 질문이 있었는데요,저는필터는 WAS와 Dispatcher Servlet 사이에서 동작하고, url을 통해 스프링과 상관없는 공통 로직

okky.kr

velog.io/@soyeon207/Spring-Filter-Interceptor-AOP#♀%EF%B8%8F-레퍼런스%EF%BB%BF4

 

[Spring] Filter, Interceptor, AOP

Filter, Interceptor, AOP 에 대해서 알아보자.

velog.io