나를 기록하다
article thumbnail
Published 2024. 3. 15. 00:23
[TIL-44/240314] Filter & Interceptor TIL
반응형

오늘은 Filter와 Interceptor에 대한 공부를 하였다. 먼저 Listener, Filter, Interceptor의 정의에 대해서 알아보고, 이번 내용의 핵심인 Filter와 Interceptor를 비교하면서 정리하고자 한다. Listener부터 시작하겠다.

 

1. Listener, Filter, Interceptor

1.1. Listener란?

  • 프로그래밍에서 Listener란 특정 이벤트가 발생하기를 기다리다가 실행되는 객체
  • 이벤트란 특정한 사건 발생
    • 버튼 클릭 ,키보드 입력, 컨테이너 빌드 완료, 웹 어플리케이션 시작, HTTP 요청 수신 등
  • 서블릿 컨테이너에서 발생하는 이벤트 감지
  • web.xml 파일에 <listener> 태그를 이용하여 사용 가능
  • 리스너가 여러 개일 경우 보통 선언된 순서대로 실행되지만 아닌 경우도 있음
    → 각각의 리스너는 독립적으로 동작할 수 있도록 설계하는 것이 좋음

1.1.1. Listener의 사용(Annotation)

예시 코드

<java />
import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; @Component public class MyListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println("Application context is refreshed: " + event); // 여기에 원하는 작업을 추가 가능 } }

 

1.2. Filter란?

  • 요청과 응답 데이터를 필터링하여 제어, 변경하는 역할
  • 사용자의 요청이 Servlet에 전달되어지기 전에 Filter를 거침
  • Servlet으로부터 응답이 사용자에게 전달되어지기 전에 Filter를 거침
  • FilterChain을 통해 연쇄적으로 동작 가능

Filter 구조

1.3. InterCeptor

1.3.1. Interceptor란?

  • HandlerInterceptor를 구현한 것
  • 요청(request)을 처리하는 과정에서 요청을 가로채서 처리
  • 접근 제어(Auth), 로그(Log) 등 비즈니스 로직과 구분되는 반복적이고 부수적인 로직 처리
  • HandlerInterceptor의 주요 메서드
    • preHandle()
    • postHandle()
    • afterCompletion()

1.3.2. preHandle

  • Controller(핸들러) 실행 이전에 호출
  • true를 반환하면 계속 진행
  • false를 반환하면 요청을 종료

1.3.3. PostHandle

  • Controller(핸들러) 실행 후 호출
  • 정상 실행 후 추가 기능 구현 시 사용
  • Controller에서 예외 발생 시 해당 메서드는 실행되지 않음

1.3.4. AfterCompletion()

  • 뷰가 클라이언트에게 응답을 전송한 뒤 실행
  • Controller에서 예외 발생 시, 네번째 파라미터로 전달이 된다. (기본은 null)
  • 따라서 Controller에서 발생한 예외 혹은 실행 시간 같은 것들을 기록하는 등 후처리 시 주로 사용

 


이제 위에서 설명한 Filter와 Interceptor를 비교해서 알아보자.

2. Interceptor - 가로채다

스프링 MVC에서 인터셉터는 웹 애플리케이션 내에서 특정한 URI 호출을 말 그대로 가로채는 역할을 한다.

인터셉터를 활용하면 기존 컨트롤러의 로직을 수정하지 않고도, 사전이나 사후에 제어가 가능하다.

쉽게 말해, 요청과 응답을 가로채서 원하는 동작을 추가하는 역할을 한다.

  • [예시 - 1] 세션을 통한 인증 구현
    • 요청을 받아들이기 전, 세션에서 로그인한 사용자가 있는지 확인하고 없다면 로그인 페이지로 redirect
    • Interceptor가 없다면 모든 컨트롤러마다 해당 로직을 넣어햐 하므로 비효율적
  • [예시 - 2] 주기적으로 결과 페이지에 등장하는 데이터들을 인터셉터에서 응답을 가로채서 데이터를 추가한 다음 보내기
    • 메일 알림 개수 조회 후 추가, 헤더 데이터 등

 

그럼 필터는 무엇일까?


3. Interceptor vs Filter

3.1. Filter

Filter는 DispatcherServlet의 처리 전 또는 후에 동작하여 사용자의 요청이나 응답의 최전방에 존재한다.

필터는 스프링의 독자적인 기능이 아닌 자바 서블릿이 제공하는 기능이다.

filter chain

위 그림처럼 Filter Chain을 통해 여러 필터가 연쇄적으로 동작하게 할 수 있다.

 

<java />
public interface Filter { public default void init(FilterConfig filterConfig) throws ServletException {} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; public default void destroy() {} }

 

3.1.1. 필터 인터페이스의 3가지 메소드

  1. init() : 필터가 웹 컨테이너에 생성될 때 실행
  2. doFilter() : Request, Response가 필터를 거칠 때 수행되는 메소드, 체인을 따라 다음에 존재하는 필터로 이동
  3. destroy() : 필터가 소멸될 때 실행

3.2. 공통점

Servlet 기술의 Filter와 Spring MVC의 HandlerInterceptor는 특정 URI에 접근할 때 제어하는 용도로 사용된다는 공통점을 가짐


3.3. 차이점

두 기능의 가장 큰 차이는 실행 시점에 속하는 영역(Context)에 있다.

Spring MVC Request Lifecycle

3.3.1. Filter

  • Filter는 동일한 웹 어플리케이션의 영역 내에서 필요한 자원들을 활용한다.
  • 웹 어플리케이션 내에서 동작하므로, 스프링의 Context를 접근하기 어렵다.

3.3.2. Interceptor

  • Interceptor의 경우 스프링에서 관리되기 때문에 스프링 내의 모든 객체(빈)에 접근이 가능하다는 차이가 있다.
  • 즉, 빈을 관리하는 스프링 컨텍스트 내에 있어서 생성된 빈들에 자유롭게 접근할 수 있다.

 

예를 들어, 추후에 살펴볼 HandlerInterceptor의 경우 스프링의 빈으로 등록된 컨트롤러나 서비스 객체들을 주입받아서 사용할 수 있기 때문에 기존의 구조를 그대로 활용하면서 추가적인 작업이 가능하다.


3.4. 차이점: 호출 시점의 차이

영역에서 차이가 나기 때문에 호출 시점도 다르다.

  • Filter : DispatcherServlet이 실행되기 전에 호출
  • Interceptor : DispatcherServlet이 실행된 후에 호출

3.5. 차이점: 용도의 차이

3.5.1. Filter

  1. 보안 관련 공통 작업
  2. 모든 요청에 대한 로깅 또는 검사
  3. 이미지/데이터 압축 및 문자열 인코딩

3.5.2. Interceptor

  1. 인증/인가 등과 같은 공통 작업
  2. Controller로 넘겨주는 정보의 가공
  3. API 호출에 대한 로깅 또는 검사

4. HandlerInterceptor

  • Filter와 유사하게 HttpServletRequest, HttpServletResponse를 인자로 받음
  • 일반적으로 Controller에서는 Servlet API(HttpServletRequest, HttpServletResponse)를 활용하는 경우는 많지 않음
  • 컨트롤러는 순수하게 파라미터와 결과 데이터를 만들어냄
    & HandlerInterceptor에서는 이러한 인자를 이용해 웹과 관련된 처리를 도와주는 역할을 수행함

 

4.1. HandlerInterceptor의 정의

<java />
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 { } }

4.1.1. preHandle

  • 지정된 컨트롤러의 동작 이전에 가로채는 역할로 사용
  • HttpServletRequest, HttpServletResponse, Object handler로 구성

4.1.2. Object Handler

  • 마지막의 Handler는 현재 실행하려는 메소드 자체를 의미
  • 이를 활용하면 현재 실행되는 컨트롤러를 파악하거나 추가적인 메소드를 실행하는 등의 작업 가능

[예시] 현재 실행되는 컨트롤러와 메소드의 정보 파악

<java />
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); System.out.println("Bean: " + handlerMethod.getBean()); System.out.println("Method: " + method); return true; }

 

위와 같이 handler를 HandleMthod 타입으로 캐스팅한 후 원래의 메소드와 객체(빈)를 확인할 수 있다.

코드 실행 시 순서대로 현재 실행되는 Controller와 메소드가 출력된다.

<java />
Bean: com.gngsn.app.controller.Home@23af30c9 Method: public java.lang.String com.gngsn.app.controller.Home.getHomePage(java.lang.Long,java.lang.Long,org.springframework.ui.Model)

4.1.3. postHandle

  • 지정된 컨트롤러의 동작 이후에 처리
  • Spring MVC의 Front Controller인 DispatcherServlet이 화면을 처리하기 전에 동작

4.1.4. 추가로 해야할 작업

컨트롤러의 실행이 끝나고 아직 화면 처리를 안 된 상태이다.

필요하다면 메소드의 실행 이후에 추가적인 작업이 가능하다.

예를 들어, 특정한 메소드의 실행 결과를 HttpSession 객체에 같이 담아야 하는 경우를 생각해보자.

  • 컨트롤러에서는 Model 객체에 결과 데이터를 저장
  • 인터셉터의 PostHandle()에서 이를 이용해 HttpSession에 결과를 담는다

위 과정을 거치면 컨트롤러에서 HttpSession을 처리할 필요가 없어진다.

 

  • [예시] result라는 변수가 저장되었다면 HttpSession 객체에 이를 보관하는 예제
<java />
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { Object result = modelAndView.getModel().get("result"); if (result != null) { request.getSession().setAttribute("result", result); response.sendRedirect("/home"); } }

postHandle()에서 'result'라는 변수가 ModelAndView에 존재하면 이를 추출해서 HttpSession에 추가한다.

HttpSession에 'result'라는 이름으로 보관한 후 /home으로 redirectfmf 수행한다.

 

<java />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>Home</title> </head> <body> <h2>${result}</h2> </body> </html>

/home이 HomeController를 통해 'home'이라는 문자열을 반환하여 home.jsp를 보여준다고 했을 때, home.jsp에서는 ${result}라는 데이터를 사용할 수 있다. 이렇게 되면 Controller 상에서 home.jsp에는 전달되는 객체가 없지만, HttpSession 객체에 필요한 정보가 보관되어 있기에 데이터가 보여진다.

 

4.1.5. afterCompletion

<java />
afterCompletion(request, response, handler, exception)

DispatcherServlet의 화면 처리(뷰)가 완료된 상태에서 처리한다.

 

4.2. HandlerInterceptorAdapter

  • HandlerInterceptor는 인터페이스로 정의되어 있음
  • HandlerInterceptorAdaptor는 인터페이스를 구현한 추상 클래스로 설계되어 있음
    → 결국 HandlerInterceptor를 구현하는 추상클래스
  • 일반적으로 Adapter라는 용어가 붙으면 특정 인터페이스를 미리 구현해서 사용하기 쉽게 하는 용도인 경우가 많음
  • HandlerInterceptorAdaptor 역시 HandlerInterceptor를 쉽게 사용하기 위해 인터페이스의 메소드를 미리 구현한 클래스
  • 추상클래스를 사용하여 불필요한 메소드까지 불러오는 일이 없앨 수 있음
  • 하지만, 인터페이스의default 메소드기능이 추가된 이후부터는 상관없어짐
    → Spring 5.3부터 HandlerInterceptorAdaptor가 Deprecated됨.자바독을 보면 아래와 같이 명시되어 있음
<java />
/* ... 생략 * @deprecated as of 5.3 in favor of implementing {@link HandlerInterceptor} * and/or {@link AsyncHandlerInterceptor} directly. */ @Deprecated public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { }

 

5. AsyncHandlerInterceptor

  • afterConcurrentHandling를 추가로 구현
<java />
public interface AsyncHandlerInterceptor extends HandlerInterceptor { default void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } }
  • Servlet 3.0부터 비동기 요청이 가능해졌음
  • 비동기 요청시 PostHandle과 afterCompletion이 수행되지 않고 afterConcurrentHandlingStarted가 수행된다.

상황에 맞는 인터페이스를 구현하자.


Spring과 SpringBoot는 김영한님의 인터넷 강의와 혼자 개인 미니 프로젝트를 진행하며 몇 번 써본 경험이 있었는데 Filter, Listener, Interceptor는 제대로 써본 경험이 없어서 이번을 계기로 공부하게 되었다. 특히 Interceptor를 필요한 장소에 사용하게 된다면 불필요한 코드의 중복을 막을 수 있을 듯하여 좋은 배움이 되었다.

반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!