[Spring Boot] 공통 에러 처리

끝으로 ㅣ 2023. 11. 26. 16:52

백엔드에서 개발 중 발생하는 에러들을 특정 코드로 정리하여 공통으로 에러 응답을 내려주는 것을 적용해보려고 한다.

 

ErrorResponse

@Data
public class ErrorResponse {
    private boolean result;
    private String code;
    private String message;

    public ErrorResponse(String code, String message) {
        this.setResult(false);
        this.setCode(code);
        this.setMessage(message);
    }
}

예외가 발생했을 때 응답할 DTO를 생성하고 code와 message를 받는 생성자를 추가했다.

 

ErrorCode

@Getter
@AllArgsConstructor
public enum ErrorCode {
    SYSTEM_ERROR("0000", "시스템 에러입니다."),
    EMPTY_DATA("0001", "데이터가 없습니다."),

    LOGIN_FAIL("1000", "ID 혹은 비밀번호가 일치하지 않습니다. 입력한 내용을 다시 확인해 주세요."),

    DUPLICATE_ID("2000", "중복된 ID가 있습니다."),
    ;

    private String errorCode;
    private String errorMessage;

    public static ErrorCode getErrorCode(ResponseStatusException responseStatusException) {
        if (!StringUtils.hasText(responseStatusException.getReason())) {
            return ErrorCode.SYSTEM_ERROR;
        }
        try {
            return ErrorCode.valueOf(responseStatusException.getReason());
        } catch (IllegalArgumentException e) {
            return ErrorCode.SYSTEM_ERROR;
        }
    }
}

ErrorCode Enum에 1000, 2000 등 개발 중 발생한 예외들을 정리한다.

getErrorCode() : ResponseStatusException을 인자로 받아서 ErrorCode Enum에 정의된 enum이 있으면 해당 enum을 반환하고, 없는 경우 공통으로 SYSTEM_ERROR를 반환한다.

 

CustomExceptionHandler 구현

@Slf4j
@RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

    /**
     * 404 Not Found Error Handling
     * @param ex the exception to handle
     * @param headers the headers to use for the response
     * @param status the status code to use for the response
     * @param request the current request
     * @return
     */
    @Override
    protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex,
            HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        log.info(ex.getMessage(), ex);
        return new ResponseEntity<>(new ErrorResponse(String.valueOf(ex.getStatusCode()), ex.getMessage()), HttpStatus.NOT_FOUND);
    }

    /**
     * 405 Method Not Allowed Error Handling
     * @param ex the exception to handle
     * @param headers the headers to use for the response
     * @param status the status code to use for the response
     * @param request the current request
     * @return
     */
    @Override
    protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
            HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatusCode status,
            WebRequest request) {
        log.info(ex.getMessage(), ex);
        return new ResponseEntity<>(new ErrorResponse(String.valueOf(ex.getStatusCode()), ex.getMessage()), HttpStatus.METHOD_NOT_ALLOWED);
    }

    /**
     * ResponseStatusException 처리
     * @param e
     * @return
     */
    @ExceptionHandler(ResponseStatusException.class)
    public ResponseEntity handleResponseStatusException(ResponseStatusException e) {
        ErrorCode errorCode = ErrorCode.getErrorCode(e);
        return new ResponseEntity<>(new ErrorResponse(errorCode.getErrorCode(), errorCode.getErrorMessage()), e.getStatusCode());
    }
}

ResponseEntityExceptionHandler를 상속받아 404, 405 에러를 오버라이드하여 처리하고,

@ExceptionHandler 어노테이션을 적용해 ResponseStatusException이 발생했을 때 만든 에러 응답 DTO를 반환한다.

 

if (memberRepository.existsByUserId(memberJoinDto.getUserId())) {
    throw new ResponseStatusException(HttpStatus.OK, ErrorCode.DUPLICATE_ID.name());
}

이제 ResponseStatusException에 ErrorCode를 넣어 던져주면 아래와 같이 공통 에러 응답을 반환할 수 있다.

{
  "result": false,
  "code": "0001",
  "message": "데이터가 없습니다."
}
{
  "result": false,
  "code": "405 METHOD_NOT_ALLOWED",
  "message": "Request method 'PATCH' is not supported"
}

 

-끝-