일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 스프링 빈
- 리눅스
- 문자열
- JavaScript
- 명령어
- map
- 자바
- 자바스크립트 코딩의 기술
- toCharArray
- 백준 java
- 코테
- Kotlin
- 리눅스마스터 1급 정리
- 개발자 회고록
- 백준 javascript
- 고잉버스
- 연습문제
- 리눅스마스터 3과목
- 리눅스마스터1급
- 코딩테스트
- Linux
- Memoir
- 스프링 컨테이너
- 프로그래머스
- 반복문
- java 백준 1차원 배열
- 카카오
- GoingBus
- 월간코드챌린지
- Java
- Today
- Total
hoon's bLog
Spring Interceptor 개념 및 동작원리 본문
Spring 관련 첫 게시물!!
사실 실무나 사이드 프로젝트 하면서, Spring은 제일 많이 쓰는 Framework라고 할 수 있다.
하지만 구동원리를 설명하라고 하면 아직도 용어가 입에 안 붙고, 어버버버 할 때가 많다.
그래서 이번 기회에 Spring을 보다 더 원론적으로 알아보기 위해,
이번 포스팅에서는 Spring의 Interceptor의 이해와 사용법을 알아보도록 하겠다.
Spring Interceptor란?
Spring MVC 에서 Interceptor는 웹 애플리케이션 내에서,
특정한 Controller의 URI 호출 통해 들어오는 요청 HttpRequest와 Controller가 응답 HttpResponse을 '가로채는' 역할을 한다.
쉽게 말해, 요청과 응답을 가로채서 원하는 동작을 추가하는 역할이다.
Interceptor를 활용하면 기존 컨트롤러의 로직을 수정하지 않고도, 사전이나 사후에 제어가 가능하다.
예를 들자면, 세션을 통한 인증을 쉽게 구현할 수 있다.
요청을 받아 들이기 전, 세션에서 로그인한 사용자가 있는지 확인하고, 없다면 로그인 페이지로 redirect 시킬 수 있다.
Interceptor가 없다면 모든 Controller마다 해당 로직을 넣어야 하니, 번거로울 뿐더러 비효율적이게 된다.
혹은, 주기적으로 결과 페이지에 등장하는 데이터들을 Interceptor에서 응답을 가로채어 데이터를 추가한 다음 보낼 수도 있다.
메일 알림 개수를 조회한 후 추가한다거나, 헤더 데이터 같은 것들이 있을 수 있다.
Interceptor VS Filter
Interceptor를 검색하다 보면 같이 등장하는 단어가 바로 Filter 다.
이 2개가 정말 잘 헷갈리니 정리를 잘해놓으면 좋을 것 같다.
Filter
Filter는 DispatcherServlet 처리 전 · 후에 동작하여 사용자의 요청이나 응답의 최전방에 존재한다.
Filter는 Spring의 독자적인 기능이 아닌 Java Servlet에서 제공한다.
위의 그림과 같이 Filter Chain을 통해 여러 Filter가 연쇄적으로 동작하게 할 수 있다.
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() {}
}
Filter Interface는 3가지 메소드를 갖고 있는데 각각 다음과 같다.
init : Filter가 웹 컨테이너에 생성될 때 실행된다.
doFilter : Request, Response가 Filter를 거칠 때 수행되는 메서드로, 체인을 따라 다음에 존재하는 Filter로 이동한다.
destroy : Filter가 소멸될 때 실행된다.
공통점 : Servlet 기술의 Filter와 Spring MVC의 HandlerInterceptor는 특정 URI에 접근할 때 제어하는 용도로 사용
차이점 : 실행 시점에 속하는 영역(Context)
Spring MVC의 LifeCycle에 대한 설명은 해당 링크를 통해 확인 할 수 있다.
Filter는 동일한 Web Application의 영역 내에서 필요한 리소스들을 활용한다.
Web Application 내에서 동작하므로, Spring의 Context를 접근하기 어렵다.
Interceptor의 경우 Spring에서 관리되기 때문에 Spring 내의 모든 객체(Bean)에 접근이 가능하다는 차이가 있다.
즉, Bean을 관리하는 Spring Context 내에 있어서 생성된 Bean들에 자유롭게 접근할 수 있다는 것!!!
예를 들어, HandlerInterceptor의 경우 Spring의 Bean으로 등록된 Controller나 Service 객체들을 주입받아서 사용할 수 있기 때문에,
기존의 구조를 그대로 활용하면서 추가적인 작업이 가능하다.
차이점 : 호출 시점의 차이
영역에서 차이가 나기 때문에 호출 시점도 다르다.
Filter는 DispatcherServlet이 실행되기 전, Interceptor는 DispatcherServlet이 실행된 후에 호출된다.
차이점 : 용도의 차이
Filter | Interceptor |
보안 관련 공통 작업 | 인증/인가 등과 같은 공통 작업 |
모든 요청에 대한 로깅 또는 감사 | Controller로 넘겨주는 정보의 가공 |
이미지/데이터 압축 및 문자열 인코딩 | API 호출에 대한 로깅 또는 감사 |
HandlerInterceptor
Filter와 유사하게 HttpServletRequest, HttpServletResponse를 인자로 받는다.
일반적으로, Controller에서는 Servlet API에 해당하는 HttpServletRequest, HttpServletResponse를
활용하는 경우는 많지 않다. (대부분 DTO나 VO를 사용)
Controller에서는 순수하게 파라미터와 결과 데이터를 만들어 내고,
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 {
}
}
각 메서드의 반환값이 true이면 Handler의 다음 동작이 실행되지만,
false이면 중단되어 남은 Interceptor와 Controller 실행 X
아래 메서드들을 제목과 같은 서체로 했다는 건, 필자가 그만큼 중요하게 생각하고,
최근까지도 제대로 정리되지 않았기 때문 ㅎㅎㅎ
preHandle(request, response, handler)
- 지정된 Controller의 동작 이전에 가로채는 역할로 사용
- 세 개의 파라미터 HttpServletRequest, HttpServletResponse, Object handler로 구성
Object Handler
마지막의 Handler는 현재 실행하려는 메서드 자체를 의미하는데,
이를 활용하면 현재 실행되는 Controller를 파악하거나, 추가적인 메서드를 실행하는 등의 작업이 가능하다.
다음은 현재 실행되는 컨트롤러와 메소드의 정보를 파악하는 예제다.
@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를 HandlerMethod 타입으로 캐스팅 후, 원래의 메서드와 객체(Bean)를 확인할 수 있다.
코드를 실행하면 순서대로 현재 실행되는 Controller와 메소드가 출력된다.
// 출력 결과
Bean: com.gngsn.app.controller.Home@23af30c9
Method: public java.lang.String co m.gngsn.app.controller.Home.getHomePage(java.lang.Long,java.lang.Long,org.springframework.ui.Model)
postHandle(request, response, handler, modelAndView)
- 지정된 컨트롤러의 동작 이후에 처리
- Spring MVC의 Front Controller인 DispatcherServlet이 화면을 처리하기 전에 동작한다.
- 비동기적 요청 처리 시에는 처리되지 않는다.
추가 작업
Controller 실행이 끝나고 아직 화면처리는 안 된 상태이므로, 필요하다면 메서드의 실행 이후에 추가적인 작업이 가능하다.
예를 들어, 특정한 메서드의 실행 결과를 HttpSession 객체에 같이 담아야 하는 경우를 생각해 볼 수 있다.
Controller에서는 Model 객체에 결과 데이터를 저장하고,
Interceptor의 PostHandle()에서 이를 이용해 HttpSession에 결과를 담는다면
Controller에서 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로 redirect를 수행
<%@ 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(request, response, handler, exception)
DispatcherSerlvet의 화면 처리(View)가 완료된 상태에서 처리한다.
(HandlerInterceptorAdapter Spring 5.3 부터 Deprecated)
HandlerInterceptor는 인터페이스로 정의되어 있지만,
HandlerInterceptorAdaptor는 인터페이스를 구현한 추상 클래스로 설계되어 있다.
결국 HandlerInterceptor를 구현하는 추상클래스다.
일반적으로 Adapter라는 용어가 붙으면 특정 인터페이스를 미리 구현해서 사용하기 쉽게 하는 용도인 경우가 많은데,
HandlerInterceptorAdaptor 역시 HandlerInterceptor를 쉽게 사용하기 위해 인터페이스의 메서드를 미리 구현한 클래스다.
추상클래스를 사용하여 불필요한 메서드까지 불러오는 일이 없앨 수 있다.
하지만, Interface의 default 메소드 기능이 추가된 이후부터는 상관없어졌다.
그래서 Spring 5.3부터 HandlerInterceptorAdaptor가 Deprecated 되었다.
Java DOC에는 아래와 같이 명시되어 있다.
/* ... 생략
* @deprecated as of 5.3 in favor of implementing {@link HandlerInterceptor}
* and/or {@link AsyncHandlerInterceptor} directly.
*/
@Deprecated
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
}
Spring에서는 HandlerInterceptor이나 AsyncHandlerInterceptor를 구현해서 사용하라고 제안하고 있다.
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가 수행된다.
참고해서 상황에 맞는 인터페이스를 구현하면 되겠다.
Reference
https://www.baeldung.com/spring-mvc-handlerinterceptor
https://goddaehee.tistory.com/154
'IT > Spring' 카테고리의 다른 글
Spring gradle project 스프링 핵심 원리 기본편 | 객체 지향 원리 적용 (15) | 2024.03.29 |
---|---|
Spring gradle project 스프링 핵심 원리 기본편 | 주문 할인 도메인 설계 (2) | 2024.03.28 |
Spring gradle project 환경설정 및 회원 가입 서비스 예제 만들기 (12) | 2024.03.21 |
Spring gradle project 스프링 핵심 원리 기본편 | 객체 지향 설계와 스프링 2 (4) | 2024.03.14 |
Spring gradle project 스프링 핵심 원리 기본편 | 객체 지향 설계와 스프링 1 (1) | 2024.03.13 |