Spring Interceptor는 클라이언트 요청을 가로채고 처리하는 데 사용됩니다. 때로는 HTTP 요청을 가로채고 컨트롤러 핸들러 메서드에 전달하기 전에 처리해야 할 때가 있습니다. 그것이 Spring MVC Interceptor가 유용한 이유입니다.
Spring Interceptor
우리는 Struts2 Interceptors와 마찬가지로
org.springframework.web.servlet.HandlerInterceptor
인터페이스를 구현하거나 HandlerInterceptor 인터페이스의 기본 구현을 제공하는 org.springframework.web.servlet.handler.HandlerInterceptorAdapter
추상 클래스를 재정의하여 자체 Spring 인터셉터를 만들 수 있습니다.
Spring Interceptor – HandlerInterceptor
Spring HandlerInterceptor는 HTTP 요청을 가로챌 위치에 따라 세 가지 메서드를 선언합니다.
- boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler): 이 메서드는 핸들러 메서드에 전달되기 전에 요청을 가로채는 데 사용됩니다. 이 메서드는 ‘true’를 반환하여 Spring에게 요청을 다른 Spring 인터셉터를 통해 처리하거나 더 이상의 Spring 인터셉터가 없는 경우 핸들러 메서드로 전송할 것을 알립니다. 이 메서드가 ‘false’를 반환하면 Spring 프레임워크는 요청이 Spring 인터셉터 자체에 의해 처리되었으며 더 이상의 처리가 필요하지 않다고 가정합니다. 이 경우 응답 객체를 사용하여 클라이언트 요청에 응답해야 합니다. handler 객체는 요청을 처리하는 선택한 핸들러 객체입니다. 이 메서드는 예외를 던질 수도 있으며, 이 경우 Spring MVC 예외 처리를 사용하여 오류 페이지를 응답으로 보낼 수 있습니다.
- void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView): 이 HandlerInterceptor 인터셉터 메서드는 HandlerAdapter가 핸들러를 호출했지만 DispatcherServlet이 아직 뷰를 렌더링하기 전에 호출됩니다. 이 메서드는 뷰 페이지에서 사용할 ModelAndView 객체에 추가 속성을 추가하는 데 사용될 수 있습니다. 이 Spring 인터셉터 메서드를 사용하여 클라이언트 요청을 처리하는 핸들러 메서드가 소요한 시간을 결정할 수 있습니다.
- void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex): 이는 핸들러가 실행되고 뷰가 렌더링된 후에 호출되는 HandlerInterceptor 콜백 메서드입니다.
만약 여러개의 스프링 인터셉터가 설정되어 있다면, preHandle() 메서드는 설정된 순서대로 실행되고, postHandle() 메서드와 afterCompletion() 메서드는 반대 순서로 호출됩니다. 우리는 컨트롤러 핸들러 메서드의 실행 시간을 기록하기 위해 간단한 Spring MVC 어플리케이션을 생성할 것입니다. 최종 Spring Interceptor 예제 프로젝트는 아래 이미지와 같이 보일 것이며, 우리는 관심 있는 구성요소를 살펴볼 것입니다.
Spring Interceptor – Controller Class
package com.journaldev.spring;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@RequestMapping(value = "/home", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
//some time lag to check interceptor execution
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
logger.info("Before returning view page");
return "home";
}
}
I am just adding some processing time in the execution of the handler method to check our spring interceptor methods in action.
Spring MVC Interceptor – HandlerInterceptorAdapter Implementation
간단하게 말해서, 나는 추상 클래스 `HandlerInterceptorAdapter`를 확장하고 있습니다. HandlerInterceptorAdapter는 HandlerInterceptor 인터페이스를 위한 추상 어댑터 클래스로, pre-only/post-only 인터셉터의 간소화된 구현을 위해 사용됩니다.
package com.journaldev.spring;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class RequestProcessingTimeInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory
.getLogger(RequestProcessingTimeInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
logger.info("Request URL::" + request.getRequestURL().toString()
+ ":: Start Time=" + System.currentTimeMillis());
request.setAttribute("startTime", startTime);
// 반환 값이 false인 경우, 'response'가 보내졌는지 확인해야 합니다
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("Request URL::" + request.getRequestURL().toString()
+ " Sent to Handler :: Current Time=" + System.currentTimeMillis());
// modelAndView에 속성을 추가할 수 있고, 그것을 뷰 페이지에서 사용할 수 있습니다
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
long startTime = (Long) request.getAttribute("startTime");
logger.info("Request URL::" + request.getRequestURL().toString()
+ ":: End Time=" + System.currentTimeMillis());
logger.info("Request URL::" + request.getRequestURL().toString()
+ ":: Time Taken=" + (System.currentTimeMillis() - startTime));
}
}
로직은 정말 간단합니다. 나는 핸들러 메소드 실행 및 뷰 페이지 렌더링을 포함한 요청 처리에 소요된 시간을 기록하고 있습니다.
Spring MVC 인터셉터 구성
우리는 스프링 인터셉터를 요청에 연결해야 합니다. 모든 인터셉터를 연결하기 위해 `mvc:interceptors` 요소를 사용할 수 있습니다. 또한 요청에 스프링 인터셉터를 포함하기 전에 URI 패턴을 매칭할 수 있도록 `mapping` 요소를 제공할 수 있습니다. 최종 스프링 빈 구성 파일 (spring.xml)은 아래와 같습니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:beans="https://www.springframework.org/schema/beans"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing
infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving
up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources
in the /WEB-INF/views directory -->
<beans:bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!-- Configuring interceptors based on URI -->
<interceptors>
<interceptor>
<mapping path="/home" />
<beans:bean class="com.journaldev.spring.RequestProcessingTimeInterceptor"></beans:bean>
</interceptor>
</interceptors>
<context:component-scan base-package="com.journaldev.spring" />
</beans:beans>
I will not explain all other components of the web application, because we are not interested in them and they don’t have any specific spring interceptor related configuration.
Spring MVC 인터셉터 응용 프로그램 테스트
서블릿 컨테이너에 애플리케이션을 배포하고 홈 컨트롤러를 호출하면 아래와 같이 로거 출력이 표시됩니다.
INFO : com.journaldev.spring.RequestProcessingTimeInterceptor - Request URL::https://localhost:9090/SpringInterceptors/home:: Start Time=1396906442086
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is en_US.
INFO : com.journaldev.spring.HomeController - Before returning view page
Request URL::https://localhost:9090/SpringInterceptors/home Sent to Handler :: Current Time=1396906443098
INFO : com.journaldev.spring.RequestProcessingTimeInterceptor - Request URL::https://localhost:9090/SpringInterceptors/home:: End Time=1396906443171
INFO : com.journaldev.spring.RequestProcessingTimeInterceptor - Request URL::https://localhost:9090/SpringInterceptors/home:: Time Taken=1085
결과는 봄 인터셉터 메서드가 정의된 순서대로 실행됨을 확인합니다. 이것으로 스프링 인터셉터 사용에 관한 설명은 모두입니다. 아래 링크에서 Spring Interceptor 예제 프로젝트를 다운로드하고 여러 인터셉터를 사용하며 다양한 구성 순서로 확인해보세요.