[Spring boot 3] ProblemDetails , MessageSource 연동방법

식빵·2024년 3월 17일
1

Spring Lab

목록 보기
29/34
post-thumbnail

Spring Boot 3 에 오면서 새롭게 선보인 ProblemDetail 클래스를 사용해보려 하는데,
계속 뭔가 조금조금씩 잘 안되더군요.
결국은 잘해냈지만 이후에 또 사용할 때 같은 실수를 안하도록 기록해봅니다.


ProblemDetail 클래스?

ProblemDetail 클래스는 IETFRFC 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



예시: ExceptionHandler 작성

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 클래스에 들어가서 확인해보시길!


message_*.properties 작성 예시

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=\
(디테일) 요청하신 사용자 정보가 조회되지 않습니다.
  • 작성방식은 다음과 같다.
    • type: problemDetail.type. + Exception 클래스 getName() 메소드 결과물
    • title: problemDetail.title. + Exception 클래스 getName() 메소드 결과물
    • detail: 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"
}



Reference

profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글