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

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

 

Listener, Filter, Interceptor

Listener란?

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

Listener의 사용(Annotation)

예시 코드

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);
        // 여기에 원하는 작업을 추가 가능
    }
}

 

Filter란?

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

Filter 구조

InterCeptor

Interceptor란?

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

preHandle

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

PostHandle

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

AfterCompletion()

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

 


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

Interceptor - 가로채다

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

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

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

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

 

그럼 필터는 무엇일까?


Interceptor vs Filter

Filter

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

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

filter chain

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

 

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

공통점

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


차이점

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

Spring MVC Request Lifecycle

Filter

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

Interceptor

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

 

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


차이점: 호출 시점의 차이

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

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

차이점: 용도의 차이

Filter

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

Interceptor

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

HandlerInterceptor

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

 

HandlerInterceptor의 정의

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

preHandle

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

Object Handler

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

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

@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와 메소드가 출력된다.

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)

postHandle

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

추가로 해야할 작업

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

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

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

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

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

 

  • [예시] result라는 변수가 저장되었다면 HttpSession 객체에 이를 보관하는 예제
@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 수행한다.

 

<%@ 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 객체에 필요한 정보가 보관되어 있기에 데이터가 보여진다.

 

afterCompletion

afterCompletion(request, response, handler, exception)

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

 

HandlerInterceptorAdapter

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

}

 

AsyncHandlerInterceptor

  • afterConcurrentHandling를 추가로 구현
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

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

profile on loading

Loading...