이번 포스팅에서는 @ControllerAdvice
, @ExceptionHandler
를 사용한 Exception Handling 방법에 대해 설명한다.
Exception은 코드 방방곡곡에서 아주 다양하게 발생한다. 이 때 handling 코드를 모든 클래스, 모든 모듈마다 심어 놓는다면 가독성은 물론 효율도 끔찍하게 떨어질 것이다.
스프링 부트에서는 이러한 문제를 해결하기 위해 @ControllerAdvice
어노테이션을 제안한다. Controller와 Custom Exception Handler에 @ControllerAdvice
을 붙이면, 그 Controller 안에서 발생한 모든 Exception은 우선 handler에게 넘겨지는 것이다.
CustomRuntimeException
프론트와 백 모두 새 유형의 Exception의 정보를 얻을 수 있도록 간단하게 name을 지정하고 확인할 수 있도록 인터페이스를 생성했다. 진짜 실무였다면 오류에 대한 자세한 정보를 섬세하게 지정해야 했겠지만, 우선은 구현 자체에 의미를 두기 위해 간단히 설정하고 넘어 갔다.
public interface CustomRuntimeException {
String getNAME();
}
NoSuchUserException
실제 만든 많은 CustomException 중 하나이다.
public class NoSuchUserException extends RuntimeException implements CustomRuntimeException {
@Getter
private final String NAME;
public NoSuchUserException(String message) {
super(message);
NAME = "NoSuchUserException";
}
}
Controller
Controller에 @ControllerAdvice
를 붙여 모든 Exception을 handler에게 넘길 수 있다.
@RestController
@ControllerAdvice
public class SomeController {...}
GlobalExceptionHandler
직접 정의하고 작성한 Exception handler는 아래와 같다. 코드가 길어 일부분만 긁어 왔다.
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
@Autowired
ObjectMapper objectMapper;
private String buildResponseJson(CustomRuntimeException e, String msg) {
Response response = Response.builder()
.header(ResponseHeader.builder()
.name(e.getNAME())
.message(msg)
.build())
.build();
return objectMapper.valueToTree(response).toPrettyString();
}
@ExceptionHandler({InvalidRequestParameterException.class})
public ResponseEntity<?> handleInvalidRequestParameterException(final InvalidRequestParameterException e) {
String msg = e.getNAME() + ": Invalid request parameter [" + e.getMessage() + "]";
log.error(msg);
return ResponseEntity.badRequest().body(buildResponseJson(e, msg));
}
@ExceptionHandler({NoResultFromDBException.class})
public ResponseEntity<?> handleNoResultFromDBException(final NoResultFromDBException e) {
log.info(e.getNAME() + ": No result found on DB [" + e.getMessage() + "]");
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@ExceptionHandler({ExpiredJwtException.class, SignatureException.class, MalformedJwtException.class})
public ResponseEntity<?> handleInvalidJwtException(final Exception e) {
String msg = "Invalid JWT exception [" + e.getMessage() + "]";
log.error(msg);
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
}
Exception handler 안에서는 @ExceptionHandler
어노테이션을 붙여 처리할 Exception의 종류를 명시해주어야 한다.
참고로, handler에게도 @ControllerAdvice
를 붙이는 이유는 handler가 전역에서 작동하게 하기 위함이라고 한다.