오늘은 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을 통해 연쇄적으로 동작 가능
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을 통해 여러 필터가 연쇄적으로 동작하게 할 수 있다.
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가지 메소드
- init() : 필터가 웹 컨테이너에 생성될 때 실행
- doFilter() : Request, Response가 필터를 거칠 때 수행되는 메소드, 체인을 따라 다음에 존재하는 필터로 이동
- destroy() : 필터가 소멸될 때 실행
공통점
Servlet 기술의 Filter와 Spring MVC의 HandlerInterceptor는 특정 URI에 접근할 때 제어하는 용도로 사용된다는 공통점을 가짐
차이점
두 기능의 가장 큰 차이는 실행 시점에 속하는 영역(Context)에 있다.
Filter
- Filter는 동일한 웹 어플리케이션의 영역 내에서 필요한 자원들을 활용한다.
- 웹 어플리케이션 내에서 동작하므로, 스프링의 Context를 접근하기 어렵다.
Interceptor
- Interceptor의 경우 스프링에서 관리되기 때문에 스프링 내의 모든 객체(빈)에 접근이 가능하다는 차이가 있다.
- 즉, 빈을 관리하는 스프링 컨텍스트 내에 있어서 생성된 빈들에 자유롭게 접근할 수 있다.
예를 들어, 추후에 살펴볼 HandlerInterceptor의 경우 스프링의 빈으로 등록된 컨트롤러나 서비스 객체들을 주입받아서 사용할 수 있기 때문에 기존의 구조를 그대로 활용하면서 추가적인 작업이 가능하다.
차이점: 호출 시점의 차이
영역에서 차이가 나기 때문에 호출 시점도 다르다.
- Filter : DispatcherServlet이 실행되기 전에 호출
- Interceptor : DispatcherServlet이 실행된 후에 호출
차이점: 용도의 차이
Filter
- 보안 관련 공통 작업
- 모든 요청에 대한 로깅 또는 검사
- 이미지/데이터 압축 및 문자열 인코딩
Interceptor
- 인증/인가 등과 같은 공통 작업
- Controller로 넘겨주는 정보의 가공
- 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를 필요한 장소에 사용하게 된다면 불필요한 코드의 중복을 막을 수 있을 듯하여 좋은 배움이 되었다.
'TIL' 카테고리의 다른 글
[TIL-46/240318] File Upload & Download (1) | 2024.03.19 |
---|---|
[TIL-45/240315] MVC 패턴 (0) | 2024.03.16 |
[TIL-43/240313] Spring MVC (1) | 2024.03.13 |
[TIL-42/240312] 관점 지향 프로그래밍(AOP) (0) | 2024.03.12 |
[TIL-41/231024] 우테코 프리코스, 빌더 패턴, 스트림, 정적 팩토리 메서드 (1) | 2023.10.24 |