Spring 은 전역적으로 예외를 처리할 수 있는 @ControllerAdvice
와 @RestControllerAdvice
어노테이션을 제공하고 있다.
@RestControllerAdvice
는 @ControllerAdvice
와 달리 @ResponseBody가 붙어 있어 응답을 Json으로 내려준다는 점에서 다르다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
...
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
...
}
ControllerAdvice 에는 @Component 어노테이션이 있어 ControllerAdvice가 선언된 클래스는 Bean
으로 등록된다.
@RestControllerAdvice 어노테이션을 활용
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoSuchElementFoundException.class)
protected ResponseEntity<?> handleNoSuchElementFoundException(NoSuchElementFoundException e) {
final ErrorResponse errorResponse = ErrorResponse.builder()
.code("Item Not Found")
.message(e.getMessage()).build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
}
@ExceptionHandler
어노테이션을 통해 어떤 예외가 발생했을때의 처리를 할지 설정 할 수 있다.
또, 전역이 아니라 패키지/모듈 별로 Exception 을 처리하고 싶다면,@RestControllerAdvice("com.example.demo.login")
와 같이 Annotation 과 함께 패키지/모듈을 명시해 주면 된다.
일반적인 실무에서 어떻게 사용하는지는 사실 명확하지 않다.
필자의 경험상 에러 인터페이스 정의를 제대로 해야한다.
무슨 얘기냐면 에러메시지로 나가는 포맷이 일정해야한다는 얘기다.
만약 로그인 모듈에서 발생한 예외에 응답하는 메세지는 에러코드랑 설명을 리턴해준다고 하고, 배송 모듈에서 발생한 예외는 에러코드랑 에러가 난 배송 번호를 리턴해준다고 하자
그러면 @ControllerAdvice
를 이용해서 통합으로 처리하려고 했지만 리턴 타입이 다르니까 통합해서 처리할 수 없다.
HTTP 상태코드, ErrorResponse같은 경우는 좋은 예다.
에러 인터페이스, 포맷이 다 같고 클라이언트 측에서도 이해하기 좋은 에러가 날라오는 것이다.
그래서 @ExceptionHandler
와 함께 @ResponseStatus(value = HttpStatus.UNAUTHORIZED)
이런 것도 집어넣어서 HTTP상태코드를 리턴하기도 한다. (앞에서 설명하진 않았지만...)
다시 한 번 정리하지만 에러 메시지가 잘 정의되어있어야 하는게 전제 조건이다.
public enum LoginErrorCode {
OperationNotAuthorized(6000,"Operation not authorized"),
DuplicateIdFound(6001,"Duplicate Id"),
//...
UnrecognizedRole(6010,"Unrecognized Role");
private int code;
private String description;
private LoginErrorCode(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
}
보통 에러를 위와 같이 한 곳에 정리를 할 것이다.
저렇게 미리 정의해놓고 실제 사용할 때는
LoginErrorCode.OperationNotAuthorized.getCode()
이런식으로 불러와서 에러객체를 만들어서 리턴할 것이다.
그러면 1차적으로 에러객체 관리는 위와 같은 방법으로 끝난다.
그 다음에 아까 배운 @ControllerAdvice
, @ExeptionHandler
를 이용해서 에러를 처리해본다.
만약 @ControllerAdvice
, @ExceptionHandler
로 처리할 때, InvalidArgumentProvided
라는 에러코드를 만들었다면, 모든 컨트롤러에서 들어오는 인자(arguments)에 대해서 한 곳에서 처리하게 되므로, 중복된 코드를 쓰지 않게 된다.
그로인해 비즈니스 로직에 더 집중할 수 있고, 코드도 간단하게 조건문에 따라 throw new XXXXException(); 하고 호출해버리면 끝나기 때문에 유지보수에 아주 큰 도움이 된다.
출처: https://jeong-pro.tistory.com/195 [기본기를 쌓는 정아마추어 코딩블로그:티스토리]