처음부터 차근차근

controller advice로 404 error 처리하는 법(gradle 사용) 본문

Framework/Spring

controller advice로 404 error 처리하는 법(gradle 사용)

_soyoung 2022. 9. 8. 17:50
반응형

controller advice의 예외 처리 원리

contoroller advice는 controller에서 예외가 생겼을 때 @ControllerAdvice나 @RestControllerAdvice를 붙인 class의 해당 예외를 핸들링하는 메서드를 호출해서 예외를 처리한다.

controller는 controller 안에 클라이언트가 입력한 주소와 mapping이 되는 메서드가 있어야하는데,

404 에러 같은 경우에는 controller안에 mapping되는 메서드가 없다보니까 controller 내에서 생긴 예외처리로 구분되지 않아서 controller advice로 예외처리하는 것이 불가능하다.

 

 

예외처리 방법

하지만 다행히도 contoroller advice 로 404에러를 해결하는 방법이 있다.

※ graddle 기준

 

application.properties

spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false

 

controller advice class

@ExceptionHandler(NoHandlerFoundException.class)
protected String handleNotFoundException(Exception e, Model model) {
    // 404 에러났을 때 실행할 코드
}

 

이 두가지를 입력하면 controller advice로도 404에러를 해결할 수 있다.

 

 

원리

spring.mvc.throw-exception-if-no-handler-found=true

이 코드는 클라이언트에서 알 수 없는 url을 입력했을 때 NoHandlerFoundException을 throw하게 하는 코드이다.

그래서 controller advice에서 NoHandlerFoundException을 받아 처리 할 수 있는 것이다.

 

이 코드의 원리는 DispatcherServlet을 보면 알 수 있다.

DispatcherServlet 클래스

public class DispatcherServlet {
  protected void doDispatch(...) throws Exception {
      mappedHandler = getHandler(processedRequest);
      if (mappedHandler == null) {
          noHandlerFound(processedRequest, response); // noHandlerFound 메서드 호출
          return;
      }
  }
  
  protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (this.throwExceptionIfNoHandlerFound) {
      throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
        new ServletServerHttpRequest(request).getHeaders()); // exception throw
    }
    else {
      response.sendError(HttpServletResponse.SC_NOT_FOUND); // 프론트에 404 응답
    }
  }
}

DispatcherServlet의 doDispatch 메소드를 보면 mappedHandler가 존재하지 않을 때, noHandlerFound 메소드가 호출된다.

noHandlerFound 메서드를 보면 throwExceptionIfNoHandlerFound 값에 따라 Exception을 throw의 여부가 결정된다.

기본 값은 false이기 때문에 프론트에 404 응답만 보낸다.

그래서 이 throwExceptionIfNoHandlerFound 값을 true로 고치면 NoHandlerFoundException을 throw할 수 있는 것이다.

 

이 설정을 true로 수정하려면 DispatcherServlet에 있는 throwExceptionIfNoHandlerFound 변수 값을 수정해야 한다.(this.throwExceptionIfNoHandlerFound 니까!)

throwExceptionIfNoHandlerFound 변수는 DispatcherServlet이 생성될 때 생성자에 의해 값이 대입된다.

DispatcherServlet의 생성자는 DispatcherServletAutoConfiguration 클래스에 정의되어있다.

 

DispatcherServletAutoConfiguration 클래스

https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.html

public class DispatcherServletAutoConfiguration {
...
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
          DispatcherServlet dispatcherServlet = new DispatcherServlet();
          dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
          dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
          dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
          dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
          dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
          return dispatcherServlet;
    }
...
}

dispatcherServlet 생성자를 보면 4번째 줄에 dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());

가 있는 것을 볼 수 있다.

이 메서드가 바로 throwExceptionIfNoHandlerFound 변수에 값을 대입하는 메서드이다.

메서드의 매개변수를 보면 webMvcProperties 클래스를 이용해서 값을 수정하는 것을 볼 수 있다.

 

webMvcProperties 클래스

@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
  ....
  private boolean throwExceptionIfNoHandlerFound = false;
  ....
}

@ConfigurationProperties은 *.properties , *.yml 파일에 있는 property를 사용할 수 있게 해주는 어노테이션이다.

prefix에 프로퍼티 파일을 쓰면 properties 파일에 있는 site-url.* 에 대하여 바인딩한다.

예를 들어 prefix 부분에다 spring.mvc라고 적으면 spring.mvc.*을 가져온다.

그래서 throwExceptionIfNoHandlerFound변수가 ~.properties에서 spring.mvc.throw-exception-if-no-handler-found 이라는 것을 유추해낼 수 있다.

 

그러므로 application.properties에서 spring.mvc.throw-exception-if-no-handler-found 값을 true로 수정한다.

그리고 실행시켜보면 controller advice가 잘 받아서 처리하는 것을 볼 수 있다.

 

 

하지만 여기서 문제가 있다.

404 에러가 떠도 ResourceHttpRequestHandler 핸들러가 계속해서 매핑이 된다.

spring boot는 내부적으로 해당하는 URL이 없으면 resource에서 파일을 조회하도록 설계되어 있다.

그래서 ResourceHttpRequestHandler 존재하지 않는 api에 매핑되는 것이다.

핸들러가 404 에러임에도 매핑되고 있음

이러한 문제때문에 모든 경우에서의 ResourceHandler 매핑 꺼야한다.

 

WebMvcAutoConfiguration addResourceHandler 메서드를 통해 ResourceProperties를 보면

ResourceProperties의 addMapping이 기본 값으로 true로 된 것을 볼 수 있다.

그러므로 아래와 같이 application.properties에서 add-mappings 값을 false로 수정해야 한다.

spring.web.resources.add-mappings=false

 

 

주의 사항 (중요)

spring.web.resources.add-mappings=false

이 설정을 하면 위에서 말했다시피 모든 resource mapping이 꺼진다.

그래서 resource에 접근하는 방식이 기존에 있었다면 반드시 별도로 설정을 해줘야한다.

예를들어 css or js 파일 접근, swagger 등을 들 수 있다.

 

예시(swagger 설정)

public class SwaggerConfig implements WebMvcConfigurer  {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/swagger-ui/*")
                .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
    }
}

 

 

 

 

<출처>

변형 및 요약 : https://tecoble.techcourse.co.kr/post/2021-11-24-spring-customize-unhandled-api/

 

SpringBoot 존재하지 않는 API 요청의 응답 커스마이징하기

SpringBoot 에서 존재하지 않는 api 요청에 대한 에러메시지를 커스터마이징한 과정을 소개한다.

tecoble.techcourse.co.kr

반응형
Comments