Back-End/Spring

[Spring] 전역 REST 컨트롤러 예외 핸들러 만들기

Henu 2022. 5. 2. 16:32

 

 

예외 처리를 REST 컨트롤러의 비즈니스 로직, 즉 API 구현부분과 분리하기 위해 @RestControllerAdvice 를 사용할 수 있다.

 

@RestControllerAdvice

@ControllerAdvice 와 @ResponseBody 가 합쳐진 어노테이션이다.

 

@ResponseBody는 컨트롤러의 return 값으로 객체를 넘길 경우 Json으로 변환해주는 어노테이션이다.

 

@ControllerAdvice는 아래 설명을 참고할 수 있다.

 

@ControllerAdvice

 

@ExceptionHandler, @ModelAttribute, @InitBinder 가 적용된 메서드들을 AOP를 적용해
컨트롤러에 적용하기 위해 만들어진 어노테이션이다.

 

  • basePackageClasses, basePackage 를 지정해서 어노테이션이 적용될 패키지 범위를 적용할 수 있다.
  • annotations에 class 를 지정해주면 클래스 단위로 범위를 적용할 수 있다.
  • 아무것도 설정해주지 않으면 전역으로 작동한다.

 

HTTP 프로토콜에 대한 에러 정보를 일관성있게 전달하는것이 좋다.

따라서 HttpErrorInfo 와 같은 클래스를 생성해서 ErrorResponse 처리에 사용할 수 있다.

@Getter
public class HttpErrorInfo {
    private final ZonedDateTime timestamp;
    private final String path;
    private final HttpStatus httpStatus;
    private final String message;

    public HttpErrorInfo(HttpStatus httpStatus, String path, String message) {
        timestamp = ZonedDateTime.now();
        this.httpStatus = httpStatus;
        this.path = path;
        this.message = message;
    }
}

 

HttpErrorInfo 는 다음과 같은 정보를 가지고 있다.

  • 현재 시간
  • Error가 발생한 request의 path
  • 에러 코드(Http status)
  • 에러 메시지

 

 

이제 RuntimeException 을 implement 하는 커스텀 예외 처리를 생성할 수 있다.

DB 에서 데이터를 조회했을 때 데이터가 없는 경우에 출력할 에러(NotFoundException)를 생성한다.

public class NotFoundException extends RuntimeException {
    public NotFoundException() {
    }

    public NotFoundException(String message) {
        super(message);
    }

    public NotFoundException(String message, Throwable cause) {
        super(message, cause);
    }

    public NotFoundException(Throwable cause) {
        super(cause);
    }
}

 

 

 

프로젝트 전역에 적용될 GlobalControllerExceptionHandler 를 생성한다.

@RestControllerAdvice
class GlobalControllerExceptionHandler {

    @ResponseStatus(NOT_FOUND)
    @ExceptionHandler(NotFoundException.class)
    public @ResponseBody
    HttpErrorInfo handleNotFoundExceptions(ServerHttpRequest request, Exception ex) {

        return createHttpErrorInfo(NOT_FOUND, request, ex);
    }
    
    private HttpErrorInfo createHttpErrorInfo(HttpStatus httpStatus, ServerHttpRequest request, Exception ex) {
        final String path = request.getPath().pathWithinApplication().value();
        final String message = ex.getMessage();

        LOG.debug("Returning HTTP status: {} for path: {}, message: {}", httpStatus, path, message);
        return new HttpErrorInfo(httpStatus, path, message);
    }
}

 

클래스 내부의 handleNotFoundExceptions 메서드

  • @ResponseStatus는 NOT_FOUND로 지정해준다.
  • ExceptionHandler의 적용 클래스를 위에서 만들었던 NotFoundException 으로 지정해준다.

 

throw new NotFoundException("message")


위와 같이 적절한 에러를 throw 하면 GlobalHandler에서 에러를 발견하고 우리가 원하는 형식으로 Response를 넘겨줄 수 있다.