본문 바로가기
Dev/Spring

HandlerInterceptor : 요청 가로채기

by vellahw 2022. 10. 19.

 

 

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가 실행된다.

  1. 컨트롤러 실행 전 : 등록된 순서대로 preHandle() 메서드 실행
  2. 컨트롤러 실행 후 : 등록된 순서의 반대로 postHandle() 메서드 실행
  3. 처리 완료 후(뷰 생성 후) : 등록된 순서의 반대로 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(2023330); //이벤트 기간 설정
        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

댓글