예외 처리를 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를 넘겨줄 수 있다.