Spring Boot 3 에 오면서 새롭게 선보인 ProblemDetail 클래스를 사용해보려 하는데,
계속 뭔가 조금조금씩 잘 안되더군요.
결국은 잘해냈지만 이후에 또 사용할 때 같은 실수를 안하도록 기록해봅니다.
ProblemDetail
클래스는 IETF
의 RFC 7807 (=Problem Details for HTTP APIs)
신규 표준을 따라 Spring
에서 새롭게 만든 클래스입니다.
이 표준은 Http API 에서 나올 수 있는 error
에 대한 표준화된 방식(템플릿)을 제공합니다.
만약 이를 잘 지키면 이후 서로 다른 Web App
, 또는 client
가
error
에 대한 더 빠른 분석이 가능하겠죠?
Problem Detail
은 아래와 같은 항목을 기본적으로 갖습니다.
type (string, URI)
: 이 문제에 대한 자세한 원인을 알려주는 문서(document)의 위치title (string)
: 문제 원인을 간결하게 표현한 제목status (integer, optional)
: HttpStatus Code (ex: 404)detail (string, optional)
: 자세하게 문제의 원인을 작성instance (string, URI, optional)
: 문제를 일으키는 요청 URL이외에도 더 필요하면 내용을 추가합니다.
더 자세한 내용은 아래 링크를 한번 읽어보시기 바랍니다.
https://zuplo.com/blog/2023/04/11/the-power-of-problem-details
package coding.toast.playground.exception;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.Date;
import java.util.Map;
@ControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
@ResponseBody
public ErrorResponse handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
// ResponseEntityExceptionHandler 에서 제공하는 createProblemDetail 메소드 사용
// 이 메소드는 exception 정보를 기반으로
// MessageSource 에게 우리가 미리 작성해둔 message_*.properties 파일에서
// 매칭되는 정보를 얻어서 ProblemDetail body 의 내용을 채운다.
ProblemDetail body
= createProblemDetail(
ex,
HttpStatus.NOT_FOUND, "no user found", null, new Object[]{}, request);
// 표준 ProblemDetail 내용인 (type, title, detail, instance 등)을
// 제외한 나머지 custom 하게 추가하고 싶은 내용들을 아래처럼 작성한다.
body.setProperties(Map.of(
"more info1", "more info111",
"more info2", "more info222"
));
// ErrorResponse 타입 그대로 반환한다.
// 또한 메소드 시그니쳐에서 @ResponseBody 도 붙여준다.
return ErrorResponse.builder(ex, body).build(getMessageSource(), LocaleContextHolder.getLocale());
}
}
참고로 ResponseEntityExceptionHandler.createProblemDetail 메소드의
정의는 다음과 같습니다.protected ProblemDetail createProblemDetail( Exception ex, HttpStatusCode status, String defaultDetail, @Nullable String detailMessageCode, @Nullable Object[] detailMessageArguments, WebRequest request) { ErrorResponse.Builder builder = ErrorResponse.builder(ex, status, defaultDetail); if (detailMessageCode != null) { builder.detailMessageCode(detailMessageCode); } if (detailMessageArguments != null) { builder.detailMessageArguments(detailMessageArguments); } return builder.build() .updateAndGetBody(this.messageSource, LocaleContextHolder.getLocale()); }
더 자세한 건 스스로 ResponseEntityExceptionHandler 클래스에 들어가서 확인해보시길!
problemDetail.type.coding.toast.playground.exception.UserNotFoundException=\
http://localhost/errors/404#user-not-found
problemDetail.title.coding.toast.playground.exception.UserNotFoundException=\
(제목) 사용자 미발견
problemDetail.coding.toast.playground.exception.UserNotFoundException=\
(디테일) 요청하신 사용자 정보가 조회되지 않습니다.
problemDetail.type.
+ Exception 클래스 getName() 메소드 결과물
problemDetail.title.
+ Exception 클래스 getName() 메소드 결과물
problemDetail.
+ Exception 클래스 getName() 메소드 결과물
이러고 에러를 일으키면 아래처럼 나온다.
{
"type": "http://localhost/errors/404#user-not-found",
"title": "(제목) 사용자 미발견",
"status": 404,
"detail": "(디테일) 요청하신 사용자 정보가 조회되지 않습니다.",
"instance": "/users/5",
"more info1": "more info111",
"more info2": "more info222"
}