Spring이 기본적으로 제공하는 HandlerMapping은 HandlerInterceptor를 이용해서 컨트롤러가 요청을 처리하기 전과 처리한 후에 알맞은 기능을 수행할 수 있도록 하고 있다.
조건에 따라 컨트롤러에 요청을 전달하지 않고 싶거나 컨트롤러가 요청을 처리한 후에 ModelAndView 객체를 조작하고 싶은 경우에 HandlerInterceptor를 사용하면 된다.
HandlerInterceptor 인터페이스는 세 개의 메서드를 정의하고 있다.
- boolean preHandle(HttpServletRequest request, HttpServletResponse, Object handler)
- void postHandle(HttpServletRequest request, HttpServletResponse, Object handler, ModelAndView modelAndView)
- void afterCompletion(HttpServletRequest request, HttpServletResponse, Object handler, Exception ex)
💡 preHandle() 메소드
preHandle() 메서드는 클라이언트의 요청을 컨트롤러에 전달하기 전에 호출된다.
세 번째 파라미터인 handler에는 컨트롤러 객체가 전달된다. 한 개 이상의 HandlerInterceptor가 체인을 형성하게 되는데, preHandle() 메서드가 false를 리턴하면 체인의 다음 HandlerInterceptor 또는 컨트롤러를 실행하지 않고 요청 처리를 종료하게 된다.
💡 postHandle() 메서드
postHandle() 메서드는 컨트롤러가 요청을 처리한 뒤에 호출된다.
HandlerInterceptor 체인에서 postHandle() 메서드는 preHandle() 메서드의 실행 순서와 반대로 실행된다. 컨트롤러 실행 도중에 예외가 발생할 경우 postHandle() 메서드는 실행되지 않는다.
💡 afterCompletion() 메서드
afterCompletion() 메서드는 클라이언트의 요청을 처리한 뒤 즉, 뷰를 통해서 클라이언트에 응답을 전송한 뒤에 실행된다.
컨트롤러가 처리하는 도중이나 뷰를 생성하는 과정에 예외가 발생하더라도 afterCompletion() 메서드는 실행된다. afterCompletion() 메서드는 preHandle() 메서드의 실행 순서와 반대로 실행된다.
🎇. HandlerInpterceptorAdapter 클래스
org.springframework.web.servlet.HandlerInpterceptor 인터페이스를 직접 구현하여 클래스를 작성할 수 있지만, 이 경우 사용하지 않는 메소드도 구현해 주어야 한다.
예를 들어 preHandle() 메서드만 필요하더라도 나머지 두 메서드도 구현해 주어야 하는 것이다.
스프링은 이런 불편함을 줄여 주기 위해 HandlerInpterceptorAdapter 클래스를 제공하고 있다.
HandlerInpterceptorAdapter 클래스는 HandlerInpterceptor 인터페이스를 구현한 클래스로서, 세 개의 메서드는 아무 기능도 제공하지 않는다.
HandlerInpterceptor 인터페이스를 구현해야 하는 클래스는 HandlerInpterceptorAdapter 클래스를 상속 받은 뒤, 세 개의 메서드 중에서 필요한 메서드 만을 오버라이딩 해서 구현해 주면 된다.
HandlerInterceptor를 구현한 뒤에는 HandlerMapping의 interceptors 프로퍼티를 사용해서 HandlerInterceptor를 등록해 주면 된다. 아래 코드는 설정 예이다.
1 2 3 4 5 6 7 8 9 | <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> <property name="alwaysUseFullPath" value="true"/> <property name="interceptors"> <list> <ref bean="eventExpirationCheckInterceptor"/> </list> </property> </bean> <bean id="eventExpirationCheckInterceptor" class="spring.interceptor.EventExpirationCheckInterceptor"/> | cs |
🎈. HandlerInterceptor의 실행 순서
HandlerMapping에는 한 개 이상의 HandlerInterceptor를 등록할 수 있는데, 이 경우 다음과 같은 순서로 HandlerInterceptor가 실행된다.
- 컨트롤러 실행 전 : 등록된 순서대로 preHandle() 메서드 실행
- 컨트롤러 실행 후 : 등록된 순서의 반대로 postHandle() 메서드 실행
- 처리 완료 후(뷰 생성 후) : 등록된 순서의 반대로 afterCompletion() 메서드 실행
예를 들어, 다음과 같이 3개의 HandlerInterceptor를 등록했다고 하자.
1 2 3 4 5 6 7 8 9 10 | <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="interceptors"> <list> <ref bean="interceptor1"/> <ref bean="interceptor2"/> <ref bean="interceptor3"/> </list> </property> ... </bean> | cs |
이 경우 클라이언트의 요청을 처리할 때 실행되는 메소드의 순서는 다음과 같다.
🎇. HandlerInterceptor 사용 예제
1) 컨트롤러 클래스 작성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package springMVC.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class EventController { @RequestMapping("/event/list.do") public String list() { return "event/list"; } @RequestMapping("/event/eventExpired.do") public String expired() { return "event/eventExpired"; } } | cs |
2) 인터셉터 클래스 작성
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | package springMVC.interceptor; import java.io.IOException; import java.util.Calendar; import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; public class EventExpirationCheckInterceptor extends HandlerInterceptorAdapter { //preHandle(): 클라이언트의 요청을 컨트롤러에 전달 하기 전에 호출됨 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //두 메소드가 true라면 if (checkEvent(request) && checkEventExpiration()) { displayExpirationPage(request, response); //리다이렉트 return false; } return true; } private boolean checkEvent(HttpServletRequest request) { return request.getRequestURI().equals( request.getContextPath() + "/event/list.do"); ///event/list.do를 요청 했다면 true 리턴 } private boolean checkEventExpiration() { Calendar calendar = Calendar.getInstance(); calendar.set(2023, 3, 30); //이벤트 기간 설정 Date now = new Date(); return now.after(calendar.getTime()); //after(): date값이 주어진 date보다 이후이면 false 리턴 } private void displayExpirationPage(HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendRedirect("eventExpired.do"); } } | cs |
3) dispatcher-servlet.xml에 설정 추가
1 2 3 4 5 6 7 8 9 10 11 | <bean class="springMVC.controller.EventController" /> <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> <property name="alwaysUseFullPath" value="true" /> <property name="interceptors"> <list> <ref bean="eventExpirationCheckInterceptor"/> </list> </property> </bean> <bean id="eventExpirationCheckInterceptor" class="springMVC.interceptor.EventExpirationCheckInterceptor" /> | cs |
4) 뷰 작성
▼list.jsp
1 2 3 4 5 6 7 8 9 10 11 12 | <%@ page language="java" contentType="text/html; charset=EUC-KR" pageEncoding="EUC-KR"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR"> <title>이벤트 목록</title> </head> <body> 이벤트 목록 출력 </body> </html> | cs |
▼eventExpired.jsp
1 2 3 4 5 6 7 8 9 10 11 12 | <%@ page language="java" contentType="text/html; charset=EUC-KR" pageEncoding="EUC-KR"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=EUC-KR"> <title>이벤트 종료</title> </head> <body> 이벤트 종료됨 </body> </html> | cs |
✨ 실행 결과
1) 이벤트 기간을 넉넉하게 지정 (현재 날짜 기준)
2) 이벤트 기간을 지난 날짜로 지정
'Dev > Spring' 카테고리의 다른 글
DispatcherServlet - alwaysUseFullPath (0) | 2022.10.20 |
---|---|
스프링 ViewResolver (0) | 2022.10.19 |
@InitBinder 어노테이션 (1) | 2022.10.18 |
스프링 Multipart : 파일 업로드 처리 (1) | 2022.10.18 |
스프링 MultipartFile 인터페이스 사용 (0) | 2022.10.18 |
댓글