package hello.login.web.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
request.setAttribute(LOG_ID, uuid);
// @RequestMapping : HandlerMethod
// 정적 리소스 : ResourceHttpRequestHandler
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler; // 호출할 컨트롤러 메서드의 모든 정보가 포함되어있다.
}
log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandler [{}]", modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String logId = (String) request.getAttribute(LogInterceptor.LOG_ID);
log.info("RESPONSE [{}][{}][{}]", logId, requestURI, handler);
if (ex != null) {
log.error("afterCompletion error!!", ex);
}
}
}
String uuid = UUID.randomUUID().toString()
uuid
를 생성한다.request.setAttribute(LOG_ID, uuid)
preHandle
에서 지정한 값을 postHandle
, afterCompletion
에서 함께 사용하려면 어딘가에 담아두어야 한다. LogInterceptor
도 싱글톤처럼 사용되기 때문에 맴버변수를 사용하면 위험하다. 따라서 request
에 담아두었다. 이 값은 afterCompletion
에서 request.getAttribute(LOG_ID)
로 찾아서 사용한다.return true
true
면 정상 호출이다. 다음 인터셉터나 컨트롤러가 호출된다if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
//호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
}
핸들러 정보는 어떤 핸들러 매핑을 사용하는가에 따라 달라진다. 스프링을 사용하면 일반적으로 @Controller
, @RequestMapping
을 활용한 핸들러 매핑을 사용하는데, 이 경우 핸들러 정보로 HandlerMethod
가 넘어온다.
@Controller
가 아니라 /resources/static
와 같은 정적 리소스가 호출 되는 경우 ResourceHttpRequestHandler
가 핸들러 정보로 넘어오기 때문에 타입에 따라서 처리가 필요하다.
종료 로그를 postHandle
이 아니라 afterCompletion
에서 실행한 이유는, 예외가 발생한 경우 postHandle
가 호출되지 않기 때문이다. afterCompletion
은 예외가 발생해도 호출 되는 것을 보장한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error");
}
//...
}
인터셉터와 필터가 중복되지 않도록 필터를 등록하기 위한 logFilter()
의 @Bean
은 주석처리하자.
WebMvcConfigurer
가 제공하는 addInterceptors()
를 사용해서 인터셉터를 등록할 수 있다.
registry.addInterceptor(new LogInterceptor())
: 인터셉터를 등록한다.order(1)
: 인터셉터의 호출 순서를 지정한다. 낮을 수록 먼저 호출된다.addPathPatterns("/**")
: 인터셉터를 적용할 URL 패턴을 지정한다.excludePathPatterns("/css/**", "/*.ico", "/error")
: 인터셉터에서 제외할 패턴을 지정한다.필터와 비교해보면 인터셉터는 addPathPatterns
, excludePathPatterns
로 매우 정밀하게 URL 패턴을 지정할 수 있다.
REQUEST [6234a913-f24f-461f-a9e1-85f153b3c8b2][/members/add]
[hello.login.web.member.MemberController#addForm(Member)]
postHandle [ModelAndView [view="members/addMemberForm";
model={member=Member(id=null, loginId=null, name=null, password=null),
org.springframework.validation.BindingResult.member=org.springframework.validat
ion.BeanPropertyBindingResult: 0 errors}]]
RESPONSE [6234a913-f24f-461f-a9e1-85f153b3c8b2][/members/add]
스프링이 제공하는 URL 경로는 서블릿 기술이 제공하는 URL 경로와 완전히 다르다. 더욱 자세하고, 세밀하게 설정할 수 있다.
자세한 내용은 다음을 참고하자.
? 한 문자 일치
* 경로(/) 안에서 0개 이상의 문자 일치
** 경로 끝까지 0개 이상의 경로(/) 일치
{spring} 경로(/)와 일치하고 spring이라는 변수로 캡처
{spring:[a-z]+} matches the regexp [a-z]+ as a path variable named "spring"
{spring:[a-z]+} regexp [a-z]+ 와 일치하고, "spring" 경로 변수로 캡처
{*spring} 경로가 끝날 때 까지 0개 이상의 경로(/)와 일치하고 spring이라는 변수로 캡처
/pages/t?st.html — matches /pages/test.html, /pages/tXst.html but not /pages/
toast.html
/resources/*.png — matches all .png files in the resources directory
/resources/** — matches all files underneath the /resources/ path, including /
resources/image.png and /resources/css/spring.css
/resources/{*path} — matches all files underneath the /resources/ path and
captures their relative path in a variable named "path"; /resources/image.png
will match with "path" → "/image.png", and /resources/css/spring.css will match
with "path" → "/css/spring.css"
/resources/{filename:\\w+}.dat will match /resources/spring.dat and assign the
value "spring" to the filename variable