용로그
article thumbnail

스프링 프레임워크는 여러개의 중복되는 작업을 제거할 수 있도록 도와주는 많은 기능을 제공한다. 그 중 가장 대표적인 것들이 필터(Filter)와 인터셉터(Interceptor)이다. 이 둘의 역할과 차이가 무엇인지 알아보자.

Filter


필터(Filter) 디스패처 서블릿에 요청이 전달되기 전/후에 어떠한 작업을 처리할 수 있는 기능을 제공한다. 디스패처 서블릿은 스프링 컨텍스트의 영역이 아닌, WAS 같은 톰캣 영역에서 관리된다. 호출 순서는 다음과 같다.

 

 

필터를 설정하려면 javax.servlet 패키지의 Filter(Interface)를 구현해야하는데, 이 클래스가 구성하고 있는 메서드는 총 3개이다. 

  • void init(FilterConfig)
  • void doFilter(ServletRequset, ServletResponse, FilterChain)
  • void destroy()
package jakarta.servlet;

import java.io.IOException;

public interface Filter {

    default void init(FilterConfig filterConfig) throws ServletException {}

    void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

    default void destroy() {}
}

 

void Init(FilterConfig)

웹 컨테이너가 서비스를 시작하고 있다는 것을 필터에게 알려주기 위해 사용된다. 서블릿 컨테이너는 필터를 인스턴스화 한 후 딱 한 번 init 메서드를 호출한다.

  • FilterConfig : 필터의 초기화를 위한 설정 정보를 제공하는 객체

필터가 필터링 작업을 수행하도록 요청받기 전에 init 메서드가 성공적으로 완료되어야 한다. 이 메서드가 실행되어 필터 객체가 초기화 되었다면, 이후의 요청들은 모두 doFilter를 거친다.

 

void doFilter(ServletRequest, ServletResponse, FilterChain)

클라이언트의 요청/응답이 체인을 통과할 때 마다 컨테이너에 의해 호출된다. 필터 체인이 이 메서드에 전달되면 필터가 요청 및 응답을 체인의 다음 엔티티로 전달할 수 있다.

  • ServletRequest : 요청에 대한 정보를 제공하는 객체
  • ServletResponse : 응답에 대한 정보를 제공하는 객체
  • FilterChain : 다음 필터 또는 서블릿으로 요청을 전달하는데 사용되는 객체

여러개의 필터가 존재할 때 다음 필터로 넘어갈 때 마다 FilterChaindoFilter 메서드를 사용해서 필요한 동작을 하도록 만들 수 있다.

 

void destroy()

웹 컨테이너가 서비스를 중단하고 있다는 것을 필터에게 알려주기 위해 사용된다. 이 메서드는 필터의 doFilter 메서드 내의 모든 스레드가 종료되거나 시간 초과가 난 경우에만 호출된다.

 

웹 컨테이너가 이 메서드를 호출한 이후 필터 인스턴스에서 doFilter 메서드를 다시 호출하지 않는다. 이 메서드가 호출되면 필터가 보유 중인 리소스(예: 메모리, 파일 핸들, 스레드)를 삭제한다.

 

인터셉터

인터셉터는 필터와 달리 스프링 프레임워크가 제공하는 기술이다. 스프링에서 직접 제공하는 기능이기 때문에 디스패처 서블릿(Dispatcher Servlet)이 컨트롤러를 호출하기 전/후에 어떠한 작업을 처리할 수 있다.

 

당연하게도 인터셉터는 스프링에서 제공하는 기능이기도 하고 디스패처 서블릿을 중심으로 처리되기 때문에 웹 컨텍스트가 아닌 스프링 컨텍스트에서 실행된다.

 

보통 디스패처 서블릿은 핸들러 매핑 API를 통해 클라이언트가 요청한 API에 맞는 컨트롤러를 찾아주는데, 찾아주는 과정에서 인터셉터가 등록되어 있다면 차례대로 거쳐서 컨트롤러가 실행된다.

 

만약 인터셉터가 존재하지 않는다면 핸들러 매핑은 곧바로 컨트롤러를 실행한다. 호출 순서는 다음과 같다.

 

 

인터셉터를 설정하려면 org.springframework.web.servlet 패키지의 HandlerInterceptor(Interface)를 구현해야하는데, 이 클래스가 구성하고 있는 메서드는 총 3개이다.

  • boolean preHandle(HttpServletRequest, HttpServletResponse, Object)
  • void postHandler(HttpServletRequest, HttpServletResponse, Object, ModelAndView)
  • void afterCompletion(HttpServletRequest, HttpServletResponse, Object, Exception)
package org.springframework.web.servlet;

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

import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;

public interface HandlerInterceptor {

	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}

}

 

boolean preHandle(HttpServletRequest, HttpServletResponse, Object)

디스패처 서블릿이 컨트롤러에게 작업을 위임하기 전에 실행된다. 보통 컨트롤러 이전에 처리해야 하는 전처리 작업 또는 요청 정보를 가공하거나 추가하는 경우에 많이 사용한다.

  • HttpServletRequest : 요청에 대한 정보를 제공하는 객체
  • HttpServletResponse : 응답에 대한 정보를 제공하는 객체
  • Object : 요청을 처리할 컨트롤러(핸들러)에 대한 정보

preHandle 메서드는 boolean 값을 반환해야 한다. 반환 값이 true면 요청을 계속 처리하고, false면 요청 처리를 중단하고 다른 위치로 리디렉션하거나 예외를 던질 수 있다.

void postHandle(HttpServletRequest, HttpServletResponse, Object, ModelAndView)

디스패처 서블릿이 컨트롤러에게 작업을 위임하고, 해당 컨트롤러의 메서드가 실행된 후에 호출된다. 3가지 매개변수는 같은 역할을 하며 ModelAndView가 하는 역할은 다음과 같다.

  • ModelAndView : 컨트롤러에서 반환된 ModelAndView 객체다. 이 객체를 통해 뷰에 전달할 데이터를 조작하거나 추가할 수 있다.

ModelAndView를 반환하는 메서드다. 하지만 최근에는 Json 형태의 데이터를 반환하도록 할 수 있는 @RestController가 생기면서 많이 사용되지 않는 메서드다.

void afterCompletion(HttpServletRequest, HttpServletResponse, Object, Exception)

디스패처 서블릿을 포함한 모든 작업이 완료된 후에 실행된다. 이 메서드도 마찬가지로 3가지 매개변수는 같은 역할을 하며 Exception이 하는 역할은 다음과 같다.

  • Exception : 요청 처리 중 발생한 예외를 나타내는 객체다. 예외가 발생하지 않은 경우 null이 반환된다.

요청 처리가 완료된 후에 호출되며, 주로 리소스 정리 또는 추가적인 작업을 수행하는데 사용된다.

 

필터와 인터셉터, 왜 사용하는가?


필터를 사용하는 이유

필터는 기본적으로 WAS 단에서 동작하기 때문에 주로 스프링과 무관한 기능들을 처리한다. 예를 들면 아래와 같은 기능들이 있다.

  • 보안 및 인증 관련 작업
  • 이미지/데이터 압축 및 문자열 인코딩
  • ...

필터는 인터셉터보다 앞단에서 동작하기 때문에 보안 처리로 올바른 요청이 아닐 경우 요청을 차단할 수 있다. 또한 웹 애플리케이션에 전반적으로 사용되는 기능을 구현하기에 적합하다.

 

인터셉터를 사용하는 이유

인터셉터는 Controller가 실행되기 전/후에 추가적인 작업을 실행할 수 있다고 했다. 대표적인 예시는 권한을 검증하는 로직등이 있다. 만약 인터셉터를 사용하지 않을 때 권한을 검증하는 로직이 필요하다면, 권한이 필요한 모든 컨트롤러에 권한을 검증하는 로직이 작성되어 있어야 한다.

 

하지만, 이 방법에는 치명적인 단점들이 존재한다.

  • 반복되는 코드 증가 : 해당 기능을 적용해야할 컨트롤러가 늘어날 수록 중복되는 코드가 비례하게 늘어난다. 이로 인해 메모리 낭비와 서버의 부하가 늘어날 수 있다. 물론 유지보수가 힘들어지는건 당연한 단점이다.
  • 코드 신뢰도 감소 : 같은 작업을 개발자가 일일이 해줘야 하다 보니, 실수가 있을 수 있다. 특히 권한 관련 로직에 문제가 생긴다면 일반 사용자가 관리자 권한을 사용할 수도 있는 것이다.

이러한 문제점을 줄이기 위한 수단으로, 인터셉터를 도입해볼 수 있다. 인터셉터를 사용하게 된다면, 개발자는 핸들러의 수 만큼 작성했던 권한 검증 로직을 인터셉터 클래스 하나로 관리할 수 있게 된다.

 

profile

용로그

@용로그

벨덩보단 용덩 github.com/wonyongChoi05