마지막으로 웹 오류에서 조금은 독특한 404 오류 처리에 대해서 살펴보자. 404 오류는 다른 오류와 달리 기본적으로 @Controller를 탈 필요가 없기 때문에 Exception으로 처리되지 않는 특성이 있다.
그럼 404 상황을 Exception으로 만들어서 처리할 수 있을까?
legacy
@ControllerAdvide에서 처리
404를 프로그래밍적으로 처리하고 싶다면 404 발생 시 예외를 발생시키도록 설정해야 한다.(기본적으로 404는 exception 상황이 아니다.) 이를 위해 web.xml에서 DispatcherServlet을 등록할 때 throwExceptionIfNoHandlerFound 초기화 파라미터를 true로 설정한다.
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/servlet-context.xml</param-value>
</init-param>
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
이제 404 상황이 발생하면 NoHandlerFoundException이 발생하므로 다른 예외와 마찬가지로 처리하면 된다.
SpringBoot
BasicErrorController 활용
BasicErrorController는 상태 코드에 따라서 오류 페이지를 연결한다. 404 발생 시 그 상태 코드가 @Controller에게 전달되기 때문에 다음과 같이 NOT_FOUND일 경우의 처리만 해주면 된다.
@Controller
public class MyErrorController extends BasicErrorController {
@Override
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
ModelAndView mnv = super.errorHtml(request, response);
HttpStatus hs = getStatus(request);
switch (hs) {
case NOT_FOUND:
mnv.setViewName("/error/404");
break;
default:
mnv.setViewName("/error/500");
break;
}
return mnv;
}
@ControllerAdvice 활용
다음으로 SpringBoot에서 @ControllerAdvice를 활용하는 방법인데 사실 이 방법은 개인적으로 사용하지 말았으면 하는 방법이다.
spring boot @Controller단에서 404를 예외로 받으려면 application.properties에 다음의 설정을 추가해야 한다.
#404 상황에서 예외를 발생시킬 것인지
spring.mvc.throw-exception-if-no-handler-found=true
일단 @ControllerAdvice는 statuc code가 아닌 Exception 기반으로 동작한다. 그런데 application.properties의 if-no-handler-found는 사실 404를 처리하는것이 아니라 글자 그대로 handler가 발견되지 않았을 때 예외를 발생시켜라는 설정이다. 그런데 SpringBoot에서는 404 상황에서 적합한 Handler를 찾지 못하면 ResourceHttpRequestHandler가 할당된다.! 결국 예외가 발생하지 않는다.
따라서 예외가 발생하지 않고 이를 처리하기 위해 추가로 아래의 설정이 필요하다.
spring.web.resources.add-mappings=false
이렇게 두 개의 설정을 추가하면 404 상황에서 예외가 발생하고 @ControllerAdvice에서 처리가 가능하다.
그런데 이렇게 하면 또 여러가지 부가적인 문제가 발생한다.
1. css, js, 심지어 fabicon이 없는 상황에서도 NoHandlerFoundException이 발생한다.(정적 resource를 매핑하지 못하기 때문이다.)
WARN 20788 --- [nio-9090-exec-2] o.s.web.servlet.PageNotFound : No mapping for GET /abc
WARN 20788 --- [nio-9090-exec-2] o.s.web.servlet.PageNotFound : No handler found for GET /abc
WARN 20788 --- [nio-9090-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.servlet.NoHandlerFoundException: No handler found for GET /abc]
이 녀석들을 다시 등록해주는 과정이 필요해진다.
2. ResourceHttpRequestHandler를 사용하는 녀석들(Swagger 등)을 위한 별도의 설정이 필요하다.
public class SwaggerConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
이처럼 오히려 신경써줘야 할 내용들이 더 많아진다.
따라서 404를 Exception 타입으로 처리하는 것 보다는 HttpStatus 기반으로 처리하는 것이 훨씬 유리하다.