🍿 강의수강/스프링MVC2편 🔒
[스프링MVC2편] 섹션 8. 예외 처리와 오류 페이지 - DispatcherType과 필터
케로⸝⸝◜࿀◝ ⸝⸝
2024. 6. 12. 10:22
1. DispatcherType
1. WAS(여기까지 전파!!) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생!!)
2. WAS(/error-page/500 다시 요청) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/500) -> View
- 예외가 발생해서 WAS까지 전파된다.
- WAS는 오류 페이지 경로를 찾아서 내부에서 오류 페이지를 호출한다. 이때 오류 페이지 경로로 필터, 서블릿, 인터셉터, 컨트롤러가 모두 다시 호출된다.
- 그런데, 로그인 인증 체크 같은 경우를 생각해 보면, 이미 필터나 인터셉터에서 로그인 체크를 모두 완료했다.
- 따라서 서버 내부에서 오류 페이지를 호출한다고 해서 해당 필터나 인터셉터가 한번 더 호출되는 것은 매우 비효율적이다.
- 결국 클라이언트로부터 발생한 정상 요청인지, 아니면 오류 페이지를 출력하기 위한 내부 요청인지 구분할 수 있어야 한다.
package jakarta.servlet;
public enum DispatcherType {
FORWARD,
INCLUDE,
REQUEST,
ASYNC,
ERROR;
private DispatcherType() {
}
}
- REQUEST: 클라이언트 요청
- ERROR: 오류 요청
- FORWARD: 서블릿에서 다른 서블릿이나 JSP를 호출할 때 RequestDispatcher.forward(request, response);
- INCLUDE: 서브릿에서 다른 서블릿이나 JSP의 결과를 포함할 때 RequestDispatcher.include(request, response);
- ASYNC: 서블릿 비동기 호출
2. DispatcherType과 필터
실습
LogFilter
package me.progfrog.exception.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.UUID;
@Slf4j
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("log filter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
String uuid = UUID.randomUUID().toString();
try {
log.info("FILTER REQUEST [uuid: {}][dispathcerType: {}][URI: {}]", uuid, request.getDispatcherType(), requestURI);
chain.doFilter(request, response);
} catch (Exception e) {
log.info("FILTER EXCEPTION!!! {}", e.getMessage());
throw e;
} finally {
log.info("FILTER RESPONSE [uuid: {}][dispathcerType: {}][URI: {}]", uuid, request.getDispatcherType(), requestURI);
}
}
@Override
public void destroy() {
log.info("log filter destroy");
}
}
webConfig
package me.progfrog.exception;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import me.progfrog.exception.filter.LogFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
return filterRegistrationBean;
}
}
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
- 이렇게 두가지를 모두 넣으면 클라이언트 요청은 물론이고, 오류 페이지 요청에도 필터가 호출된다.
- 아무것도 넣지 않으면 기본 값이 DispatcherType.REQUEST이다. 즉, 클라이언트의 요청이 있는 경우에만 필터가 적용된다.
- 특별히 오류 페이지 경로도 필터를 적용할 것이 아니라면, 기본값을 그대로 사용하면 되고 오류 페이지 전용 필터를 적용하고 싶다면 DispatcherType.ERROR만 지정하면 된다.
확인
- LogFilter가 호출되어, log.info("FILTER REQUEST [uuid: {}][dispathcerType: {}][URI: {}]", uuid, request.getDispatcherType(), requestURI); 로그 남음
- chain.doFilter(request, response);로 다음 필터나 서블릿 지나서 ServletExController의 "/error-ex"에 매핑된 errorEx() 호출
- 응답이 돌아와서 try ~ catch 문에서 예외를 잡아 log.info("FILTER EXCEPTION!!! {}", e.getMessage()); 로그 남음
- 그 다음에 finally가 무조건 호출되므로 log.info("FILTER RESPONSE [uuid: {}][dispathcerType: {}][URI: {}]", uuid, request.getDispatcherType(), requestURI); 로그 남음
- 예외를 throw e;라고 던졌기 때문에, 예외가 WAS까지 올라왔다가 오류 페이지를 출력하기 위해 WAS 내부에서 다시 호출이 발생
- log.info("FILTER REQUEST [uuid: {}][dispathcerType: {}][URI: {}]", uuid, request.getDispatcherType(), requestURI); 로그가 남는데, 이때 DispatcherType은 ERROR 인 것을 확인할 수 있음
- 오류 페이지 출력을 다하고 나서, 마지막에 finally 로그 남는 것까지 확인
반응형