Kim VamPa

[spring] Spring Interceptor 란?(HandlerInterceptor, HandlerInterceptorAdapter) 본문

공부/스프링

[spring] Spring Interceptor 란?(HandlerInterceptor, HandlerInterceptorAdapter)

Kim VamPa 2021. 1. 13. 17:27
728x90
반응형

목표

  •  Interceptor 란 무엇인지 알아본다.
  •  Interceptor를 직접 구현해본다.

 

 

순서

1. 인터셉터(Interceptor)

   1.1 인터셉터란?

   1.2 왜 사용하는가?

   1.3 구현 수단

   1.4 어떤 메서드를 가지고 있는가?

2. 인터셉터 동작 위치 및 순서

3. 구현 방법(in SpringFramework) 및 실습

4. 다중 인터셉터

5. HandlerInterceptor 메서드 출력 순서

 

 

1. 인터셉터(Interceptor)

1.1 인터셉터란?

컨트롤러(Controller)의 '핸들러(Handler)'를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 일종의 필터

 intercept라는 단어는 '낚아채다'라는 의미입니다. 해당 단어의 의미와 같이 사용자 요청에 의해 서버에 들어온 Request 객체를 컨트롤러의 핸들러(사용자가 요청한 url에 따라 실행되어야 할 메서드, 이하 핸들러)로 도달하기 전에 낚아채서 개발자가 원하는 추가적인 작업을 한 후 핸들러로 보낼 수 있도록 해주는 것이 인터셉터(Interceptor)입니다.

 

 

1.2 왜 사용하는가?

 개발자는 특정 Controller의 핸들러가 실행이 되기 전이나 후에 추가적인 작업을 원할 때 Interceptor를 사용합니다. (추가적인 작업으로는 로그인 체크, 권한 체크 등이 있습니다.)

 

 권한 체크 예를 통해서 개발자가 인터셉터의 어떠한 이점 때문에 사용하기를 원하는지 살펴보겠습니다. 개발자가 관리자 계정만이 실행할 수 있는 Controller 핸들러를 작성한다고 가정하겠습니다. 개발자는 오직 관리자 계정만 실행할 수 있도록 하기 위해 핸들러에 접근하는 사용자가 관리자인지 확인하는 세션 체크 코드를 각 핸들러에 작성해주어야 합니다. 작성해주어야 할 핸들러가 수가 적다면 문제가 되지 않습니다. 하지만 적용해야 할 핸들러가 수천 개가 된다면 어떻게 될까요?

 

 크게 두 가지 문제가 생깁니다. 첫 번째 문제로 메모리 낭비, 서버의 부하가 늘어나는 것입니다. 적용해야 할 핸들러 수만큼 세션 체크 코드를 작성함으로써 반복되는 코드들이 매우 많아지기 때문입니다. 두 번째 문제로 코드의 누락에 대한 걱정입니다. 사람이 작성을 하는 것이기 때문에 누락과 같은 실수가 발생할 수밖에 없습니다. 만약 회원 정보에 접근하는 핸들러가 세션 체크가 누락되어서 관리자인지 확인을 안 한다면 자격이 없는 사용자가 접근할 수 있게 되어 보안적으로 큰 문제를 가지게 됩니다. 이를 예방하기 위해 개발자는 직접이든 어떠한 도구를 통해서든 세션 체크 코드의 누락을 일일이 확인을 해야 하는데 이는 매우 큰 낭비입니다.

 

 이러한 문제점들을 줄이기 위한 수단으로써 개발자는 인터셉터를 사용할 수 있습니다. 인터셉터를 사용하게 되면 개발자는 핸들러 수만큼 작성했던 세션 체크 코드를 인터셉터 클래스에 한 번만 작성하면 됩니다. 이로 인해 코드의 량이 현저히 줄기 때문에 메모리 낭비를 줄일 수 있게 됩니다. 그리고 인터셉터 적용 유무의 기준이 되는 url을 servelt-conetext.xml에 설정을 해주게 되면 스프링에서 일괄적으로 해당 url 경로의 핸들러에 인터셉터를 적용해주기 때문에 누락에 대한 위험이 상당히 줄게 됩니다.

 

 

1.3 구현 수단

 스프링에서 제공하는  org.springframework.web.servlet.HandlerInterceptor  인터페이스를 구현하거나,  org.springframework.web.servlet.handler.HandlerInterceptorAdapter  추상 클래스를 오버라이딩 함으로써 자신만의 인터셉터를 만들 수 있습니다. HandlerInterceptorAdapter 추상 클래스 경우 HandlerInterceptor 인터페이스를 상속받아서 구현되었습니다. 

 

HandlerInterceptor API(Spring 5.3.2버전)

HandlerIntreceptorAdapter API(Spring 5.3.2버전)

 

1.4 어떤 메서드를 가지고 있는가?

 스프링에서 제공해주는 HandlerInterceptor 인터페이스와 HandlerInterceptorAdapter 추상 클래스에 정의되어 있는 메서드는 preHandle(), postHandle(), afterCompletion() 3가지입니다. 

 

가. preHandle()

  •  컨트롤러가 호출되기 전에 실행됩니다.
  •  컨트롤러가 실행 이전에 처리해야 할 작업이 있는 경우 혹은 요청정보를 가공하거나 추가하는 경우에 사용합니다.
  •  실행되어야 할 '핸들러'에 대한 정보를 인자 값으로 받기 때문에 '서블릿 필터'에 비해 보다 세밀하게 로직을 구성할 수 있습니다.
  •  리턴 값이 boolean입니다. ture를 리턴하게 된다면 preHandle() 실행 후 핸들러에 접근을 합니다. false를 리턴하게 되면 작업을 중단하기 때문에 컨트롤러와 남은 인터셉트가 실행되지 않습니다. 

 

나. postHandle()

  •  핸들러가 실행은 완료되었지만 아직 View가 생성되기 이전에 호출됩니다.
  •  ModelAndView 타입의 정보가 인자 값으로 받습니다. 따라서 Controller에서 View에 정보를 전달하기 위해 작업한 Model 객체의 정보를 참조하거나 조작할 수 있습니다.
  •  preHandle()에서 리턴 값이 false인 경우 실행되지 않습니다.
  •  적용 중인 interceptor가 여러 개인 경우 preHandle()는 역순으로 호출됩니다.
  •  비동기적 요청 처리 시에는 처리되지 않는다.

 

다. afterCompletion()

  •  모든 View에서 최종 결과를 생성하는 일을 포함한 모든 작업이 완료된 후에 실행됩니다.
  •  요청 처리 중에 사용한 리소스를 반환해주기 적당한 메서드입니다.
  •  preHandle()에서 리턴 값이 false인 경우 실행되지 않습니다.
  •  적용 중인 interceptor가 여러 개인 경우 preHandle()는 역순으로 호출됩니다.
  •  비동기적 요청 처리 시에는 호출되지 않습니다.

 

 

2. 인터셉터 동작 위치 및 순서

사용자는 서버에 자신이 원하는 작업을 요청하기 위해 url을 통해서 Request 객체를 보냅니다. DispatcherServelt은 해당 Request 객체를 받어서 분석한 뒤 '핸들러 맵핑(HandlerMapping)' 에게 사용자의 요청을 처리할 핸들러를 찾도록 요청합니다. 그 결과로 핸들러 실행 체인(HandlerExecutionChanin)이 동작하게 되는데, 이 핸들러 실행 체인은 하나 이상의 핸들러 인터셉터를 거쳐서 컨트롤러가 실행될 수 있도록 구성되어 있습니다. 

 핸들러 인터셉터를 등록하지 않았다면 곧바로 컨트롤러가 실행이 됩니다. 반대로 하나 이상의 인터셉터가 지정이 되어 있다면 지정된 순서에 따라서 인터셉터를 거쳐서 컨트롤러를 실행합니다.

 

그림 1

 

 

3. 구현 방법 및 실습

3.1 구현 방법

 Springframework에서 Interceptor를 적용하기 위한 큰 틀은 다음과 같습니다.

  1. HandlerInterceptor 혹은 HandlerInterceptorAdater를 상속받아서 자신만의 Interceptor 클래스를 생성합니다.
  2. servlet-context.xml에 우리가 작성한 Interceptor 클래스를 빈(bean)으로 등록해주고, Interceptor를 적용할 url을 작성해줍니다.

 

3.2 실습

 저는 스프링프레임워크에서 MVC 프로젝트(Spring Legacy Project)를 처음 만들게 되었을 때 기본적으로 작성되어 있는 HomeController와 home.jsp를 대상으로 Interceptor 구현 및 테스트를 진행해보겠습니다. 

 

. Spring-web를 pom에 추가합니다. 버전은 자신이 사용하고 있는 스프링 버전과 맞춰주시면 됩니다. 

※ 스프링 프로젝트를 생성시에 pom.xml에 spring-webmvc <dependency>가 있다면 추가해줄 필요가 없습니다. spring-webmvc 라이브러리는 spring-web 라이브러리를 포함하고 잇기 때문입니다. 그럼에도 이 부분을 작성한 이유는 Intreceptor기술이 spring-web 라이브러리에 속한 기술이라는 점을 말하기 위함입니다. 

 

1
2
3
4
5
6
7
 
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework-version}</version>
        </dependenc
 

 

그림 2

. Interceptor 클래스를 작성합니다

 

 먼저 Interceptor 클래스만 따로 관리하기 위해서 패키지를 따로 만들어 주었습니다. 

 

그림 3

 

 자신이 원하는 이름의 클래스(class)를 생성한 후 해당 클래스에 HandlerInterceptor, HandlerIntreceptor Adapter 둘 중 하나를 선택하여 상속을 해주시면 됩니다. 상속 시에 차이점은 HandlerInterceptor 경우 인터페이스이기 때문에 implements 키워드를, HandlerInterceptorAdapter는 추상 클래스이기 때문에 extends를 키워드를 사용해야 합니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
// HandlerInterceptor 상속 받을 경우
public class CustomInterceptor implements HandlerInterceptor
{
 
}
 
// HandlerInterceptorAdaptor 상속 받을 경우
public class CustomInterceptor extends HandlerInterceptorAdapter
{
 
}
 

 

그림 4

 

그림 5

 

 

 메서드는 preHandle(), postHandle(), afterCompletion()를 모두 사용해보겠습니다. 모든 메서드 구현부에는 는 공통적으로 메서드의 이름을 출력하도록 코드를 추가하였습니다. (단순히 테스트를 위한 목적이기 때문에 logger 기록이 아닌 println() 메서드를 사용하였습니다.

 

* 부모 class의 메서드를 쉽게 작성하기 위해 이클립스에서 제공하는 'Override/Implement Methods'를 사용하였습니다.

 

더보기
그림 6

 

그림 7

 

 

 

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
public class CustomInterceptor implements HandlerInterceptor {
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        
        System.out.println("preHandle1");
        
        
        
        return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        
        System.out.println("postHandle1");
        
        
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        
        System.out.println("afterCompletion1");
        
        
    }    
    
}
 

 

그림 8

 preHandler() 메서드에는 인자값으로 받는 request와 handler의 정보를 출력하도록 코드를 추가하였습니다. (해당 정보들은 postHandle(), afterCompletion() 메서드에서도 동일한 출력값을 가지기 때문에 preHandle() 에만 작성하였습니다.

 

* return 값에 false를 입력할 경우 위에서 설명한 것처럼 Contorller로 넘어가지 않습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        
        System.out.println("preHandle1");
        System.out.println("[preHandle][" + request + "]" + "[" + request.getMethod() + "]" + request.getRequestURI());
        System.out.println("[handler][" + handler.toString() + "]");
        
        
        return true;
    }
 

 

그리 9

 

 postHandle() 메서드에는 Contorller에서 Model에 담은 정보("serverTime")를 인자값으로 들어오는 ModelAndView를 통해서 출력시켜보겠습니다.

 

그림 10 HomeContoller.java

 

1
2
3
4
5
6
7
8
9
10
11
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        
        System.out.println("postHandle1");
        System.out.println("[ModelAndView][ model 이름 : " + modelAndView.getViewName() + "][ model 내용 :" + modelAndView.getModel() + "]" );
        
        
    }
 

 

그림 11

 

다. 작성한 Interceptor 클래스를 스프링에서 인식할 수 있도록 빈으로 등록해주어야 합니다. Interceptor가 Servlet 단계에서 작동하는 것이기 때문에 servlet-context.xml에 설정을 해주어야합니다. 우리가 만든 Interceptor 클래스를 빈으로 등록해주는 코드와 Interceptor를 어떠한 url에 적용시킬지를 설정하는 코드를 추가합니다. (프로젝트 생성 후 아무것도 추가하지 않았기 때문에 만들어진 url이 '/home' 밖에 없습니다. 따라서 설정에는 '/home' 입력해주거나 전체 url에 적용되도록'/**'를 입력해주시면 됩니다.

 

1
2
3
4
5
6
7
8
 
    <interceptors>
           <interceptor>
               <mapping path="/**"/>
                   <beans:bean id="commonInterceptor" class="com.vam.interceptor.CustomInterceptor"/>
        </interceptor>
    </interceptors>
 

 

그림 12

 

. 서버를 실행시켜 테스트를 해봅니다. 저의 경우 Tomcat server의 경로를 전혀 설정해주지 않았기 때문에 '/home'을 실행하기 위해서 'http://localhost:8080/controller/home'를 입력하였습니다. 

 

그림 13

 

 

그림 14

 

 

 파랑은 prehandle(), 빨강은 posthandle(), 노랑은 aterCompletion()입니다. Request, Handler, ModelAndView의 정보가 정상적으로 출력된 것을 확인할 수 있습니다. 

 

 

4. 다중 인터셉터

 하나의 url 한 개 이상의 인터셉터를 적용시킬 수 있습니다. 직접 적용해보겠습니다.

 

 추가로 적용시킬 Interceptor 클래스를 작성하였습니다. 메서드는 3개 모두 작성하였으며 2번째 Interceptor 이기 때문에 각 메서드의 구현부에 '메서드 이름 + 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
 
public class SubInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        
        System.out.println("preHandle2");
                
        return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        
        System.out.println("postHandle2");
                
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        
        System.out.println("afterCompletion3");
                
    }
    
}
 

 

그림 15 새로 추가한 Interceptor class

 

1
2
3
4
5
6
7
8
9
10
11
12
13
 
    <interceptors>
           <interceptor>
              <mapping path="/**"/>
               <beans:bean id="commonInterceptor" class="com.vam.interceptor.CustomInterceptor"/>
        </interceptor>
        
        <interceptor>
            <mapping path="/**"/>
            <beans:bean id="subInterceptor" class="com.vam.interceptor.SubInterceptor"/>
        </interceptor>
    </interceptors>
 

 

그림 16 servlet-context.xml 설정

 

 결과는 아래와 같습니다. 결과를 보시면 servlet-context.xml에 Interceptor를 작성한 순으로 출력되는 것을 확인할 수 있습니다. 특이한 점은 컨트롤러를 거친 후 perhandler(), afterCompletion() 메서드는 역순으로 출력된다는 점입니다. 

 

그림 17

 

REFERENCE

 

DATE

  • 2021.01.13
728x90
반응형
Comments