[Spring] 스프링 에러 처리 (Error Handling for REST with Spring)

ack·2022년 2월 21일
0

Spring

목록 보기
5/5
post-thumbnail

1 방법 1 – Controller에서 @ExceptionHandler

@Controller 레벨 에서 작동합니다. 예외를 처리하는 메소드를 정의하고 @ExceptionHandler어노테이션을 사용합니다.

public class FooController{

    //...
    @ExceptionHandler({ CustomException1.class, CustomException2.class })
    public void handleException() {
        //
    }

@ExceptionHandler어노테이션이있는 메소드는 전체 애플리케이션이 아니라 특정 컨트롤러에 대해서만 활성화 됨 → 예외 처리 메커니즘에 적합하지 않음

모든 컨트롤러가 기본 컨트롤러 클래스를 상속하도록 하여 제한을 해결할 수 있지만, 불가능한 경우가 있을 수 있음

2 방법 2 – HandlerExceptionResolver 정의하기

응용 프로그램에서 발생한 모든 예외를 해결할 수 있고, REST API에서 균일 한 예외 처리 메커니즘을 구현할 수 있습니다.

2.1. ExceptionHandlerExceptionResolver

Spring 3.1에서 도입되었으며 기본적으로 DispatcherServlet에서 활성화됩니다. 앞에 언급한 @ExceptionHandler 메커니즘이 작동 하는 방식의 핵심 구성 요소입니다.

Spring 예외를 해당 HTTP 상태 코드, 즉 클라이언트 오류 – 4xx 및 서버 오류 – 5xx 상태 코드 로 해결하는 데 사용 (처리되는 스프링 예외의 전체목록)합니다.

응답의 상태 코드를 올바르게 설정하지만 한 가지 제한 사항은 응답 본문에 아무것도 설정하지 않는다는 것입니다.

또한 REST API의 경우 상태 코드는 실제로 클라이언트에게 제공 할 정보가 충분하지 않습니다. 응답에 본문이 있어야 응용 프로그램이 실패에 대한 추가 정보를 제공 할 수 있습니다.

2.3. ResponseStatusExceptionResolver

이 Resolver는 Spring 3.0에서도 도입되었으며 기본적으로 DispatcherServlet 에서 활성화됩니다 .

주된 책임은 사용자 지정 예외에서 사용 가능한 @ResponseStatus 어노테이션 을 사용하여 이러한 예외를 HTTP 상태 코드에 매핑하는 것입니다.

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class MyResourceNotFoundException extends RuntimeException {
    public MyResourceNotFoundException() {
        super();
    }
    public MyResourceNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    public MyResourceNotFoundException(String message) {
        super(message);
    }
    public MyResourceNotFoundException(Throwable cause) {
        super(cause);
    }
}

DefaultHandlerExceptionResolver와 동일하게 ,이 리졸버는 응답의 본문을 처리하는 방식에 제한이 있습니다.
응답에 상태 코드를 맵핑하지만 본문은 여전히 null입니다.

2.4. SimpleMappingExceptionResolver 및 AnnotationMethodHandlerExceptionResolver

SimpleMappingExceptionResolver는 꽤 많은 시간 동안 있었는데, 기존의 스프링 MVC 모델로 나오고 있고 REST 서비스에 매우 관련되어 있지 않습니다. 기본적으로 예외 클래스 이름을 표시하여 이름을 봅니다.

AnnotationMethodHandlerExceptionResolver@ExceptionHandler 어노테이션을 통해 예외 처리하도록 도입 되었습니다. 그러나 Spring3.2ExceptionHandlerExceptionResolver에 의해 거의 사용되지 않음

2.5. Custom HandlerExceptionResolver

DefaultHandlerExceptionResolverResponseStatusExceptionResolver 의 조합은 Spring RESTful 서비스에 대한 좋은 오류 처리 메커니즘을 제공하기 위해 먼 길을 간다. 단점은 앞에서 언급했듯이 응답 본문을 제어하지 않습니다.

이상적으로는 클라이언트가 요청한 형식 (Accept 헤더 를 통해)에 따라 JSON 또는 XML을 출력 할 수 있기를 원합니다 .

이것만으로도 새로운 사용자 정의 예외 해결 프로그램을 만들 수 있습니다 .

@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {
 
    @Override
    protected ModelAndView doResolveException(
      HttpServletRequest request,
      HttpServletResponse response,
      Object handler,
      Exception ex) {
        try {
            if (ex instanceof IllegalArgumentException) {
                return handleIllegalArgument(
                  (IllegalArgumentException) ex, response, handler);
            }
            ...
        } catch (Exception handlerException) {
            logger.warn("Handling of [" + ex.getClass().getName() + "]
              resulted in Exception", handlerException);
        }
        return null;
    }
 
    private ModelAndView
      handleIllegalArgument(IllegalArgumentException ex, HttpServletResponse response)
      throws IOException {
        response.sendError(HttpServletResponse.SC_CONFLICT);
        String accept = request.getHeader(HttpHeaders.ACCEPT);
        ...
        return new ModelAndView();
    }
}

여기서 주목할 점은 요청 자체에 액세스 할 수 있으므로 클라이언트가 보낸 Accept 헤더의 값을 고려할 수 있다는 것 입니다.

예를 들어, 클라이언트가 application / json 을 요청 하면 오류 조건의 경우 application / json으로 인코딩 된 응답 본문을 반환해야 합니다.

다른 중요한 구현 세부 사항은 ModelAndView를 반환 한다는 것 입니다. 이것은 응답의 본문 이며 필요한 것을 설정할 수 있습니다.

이 접근법은 Spring REST Service의 오류 처리를위한 일관되고 쉽게 구성 가능한 메커니즘입니다. 그러나 저수준 HtttpServletResponse 와 상호 작용하고 ModelAndView를 사용하는 이전 MVC 모델에 적합 하므로 여전히 개선의 여지가 있습니다.

3. Solution 3: @ControllerAdvice

Spring 3.2@ControllerAdvice어노테이션으로 전역 @ExceptionHandler를 지원

이를 통해 이전 MVC 모델 에서 탈피하고 @ExceptionHandler 의 유형 안전성 및 유연성과 함께 ResponseEntity 를 사용하는 메커니즘을 사용할 수 있습니다 .

@ControllerAdvice
public class RestResponseEntityExceptionHandler
  extends ResponseEntityExceptionHandler {
 
    @ExceptionHandler(value
      = { IllegalArgumentException.class, IllegalStateException.class })
    protected ResponseEntity<Object> handleConflict(
      RuntimeException ex, WebRequest request) {
        String bodyOfResponse = "This should be application specific";
        return handleExceptionInternal(ex, bodyOfResponse,
          new HttpHeaders(), HttpStatus.CONFLICT, request);
    }
}

@ControllerAdvice의 어노테이션은 이전의 여러개로 흩어져 있는 @ExceptionHandler들을 하나의 글로벌 오류 처리 컴포넌트로 통합합니다.

실제 메커니즘은 매우 간단하지만 매우 유연합니다.

응답 본문 및 상태 코드에 대한 모든 권한 제공

여러 가지 예외를 동일한 방법으로 매핑하여 함께 처리

최신 RESTful ResposeEntity응답 을 잘 활용합니다.

@ExceptionHandler 로 선언 된 예외를 메소드의 인수로 사용 된 예외 와 일치 시키는 것 입니다. 이것이 일치하지 않으면 컴파일러는 불평하지 않습니다 – 아무 이유도없고 스프링도 불평하지 않습니다.

그러나 실제로 런타임에 예외가 발생 하면 예외 해결 메커니즘이 실패합니다 .

java.lang.IllegalStateException: No suitable resolver for argument [0][type=...] HandlerMethod details: ...

4. Solution 4: ResponseStatusException (Spring 5 and Above)

Spring5는 ResponseStatusException 클래스를 도입했습니다. HttpStatusreason, cause를 제공하는 인스턴스를 만들 수 있습니다.

@GetMapping(value = "/{id}")
public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) {
    try {
        Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
 
        eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
        return resourceById;
     }
    catch (MyResourceNotFoundException exc) {
         throw new ResponseStatusException(
           HttpStatus.NOT_FOUND, "Foo Not Found", exc);
    }
}

ResponseStatusException 의 장점

  • 프로토타이핑에 탁월함: 기본 솔루션을 매우 빠르게 구현할 수 있습니다.
  • 하나의 유형, 다중 상태 코드: 하나의 예외 유형이 다중 다른 응답으로 이어질 수 있습니다. 이것은 @ExceptionHandler 에 비해 결합도가 낮습니다.
  • 사용자 정의 예외 클래스를 많이 만들 필요가 없습니다.
  • 프로그래밍 방식으로 예외를 생성할 수 있으므로 예외 처리를 더 잘 제어 할 수 있습니다.
  • 하나의 애플리케이션 내에서 다양한 접근 방식을 결합하는 것이 가능합니다
    예를 들어, @ControllerAdvice 를 전역적으로 구현할 수 있지만 ResponseStatusException 도 로컬로 구현할 수 있습니다.

5. Spring Boot: ErrorController

Spring Boot는 합리적인 방식으로 오류를 처리하기 위해 ErrorController 구현을 제공합니다.
브라우저에 대한 폴백 오류 페이지와 RESTful, non-HTML 요청에 대한 JSON 응답을 제공합니다.

{ 
  "timestamp": "2019-01-17T16:12:45.977+0000", 
  "status": 500, 
  "error": "Internal Server Error", 
  "message": "Error processing the request!", 
  "path": "/my-endpoint-with-exceptions" 
}

이 외에도, 오류에 대한 자체 보기 해석기 매핑을 제공 하여 Whitelabel 페이지를 재정의할 수 있습니다.

컨텍스트에 ErrorAttributes Bean을 포함하여 응답에 표시하려는 속성을 사용자 정의할 수도 있습니다. Spring Boot에서 제공하는 DefaultErrorAttributes 클래스를 확장하여 더 쉽게 만들 수 있습니다.

@Component
public class MyCustomErrorAttributes extends DefaultErrorAttributes {
 
    @Override
    public Map<String, Object> getErrorAttributes(
      WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes =
          super.getErrorAttributes(webRequest, options);
        errorAttributes.put("locale", webRequest.getLocale()
            .toString());
        errorAttributes.remove("error");
 
        //...
 
        return errorAttributes;
    }
}

또한, 특정 콘텐츠 유형에 대한 오류를 처리하는 방법을 정의(또는 재정의)하려면 ErrorController Bean을 등록할 수 있습니다. Spring Boot에서 제공 하는 기본 BasicErrorController 를 사용할 수 있습니다.

예를 들어 애플리케이션이 XML 끝점에서 트리거된 오류를 처리하는 방법을 사용자 지정한다고 상상해 보십시오. @RequestMapping 을 사용하여 public 메소드를 정의하고 application/xml 미디어 유형 을 생성한다고 명시하기만 하면 됩니다.

@Component
public class MyErrorController extends BasicErrorController {
 
    public MyErrorController(
      ErrorAttributes errorAttributes, ServerProperties serverProperties) {
        super(errorAttributes, serverProperties.getError());
    }
 
    @RequestMapping(produces = MediaType.APPLICATION_XML_VALUE)
    public ResponseEntity<Map<String, Object>> xmlError(HttpServletRequest request) {
         
    // ...
 
    }
}

참고

관련 Github 코드
exception-handling-for-rest-with-spring을 읽고 정리한 내용입니다.

profile
아자 (*•̀ᴗ•́*)و

0개의 댓글