🙏내용에 대한 피드백은 언제나 환영입니다!!🙏
우선 내가 @ExceptionHandler를 통해서 글로벌 예외처리를 한 이유는 아래와 같다.
이제 @ExceptionHandler에 대해서 알아보겠다.
@ExceptionHandler는 스프링 프레임워크에서 사용되는 예외 처리 어노테이션이다. 컨트롤러 클래스 내에서 특정 예외가 발생했을 때 해당 예외를 처리할 메서드를 지정하는 데 사용된다. 그래서, 예외가 발생하면 사용자가 정의한 로직을 실행하거나 적절한 응답을 반환할 수 있습니다.
('컨트롤러 클래스 내에서 특정 예외가 발생했을 때' => 컨트롤러에 작성되어 있는 메서드에서 호출하는 다른 클래스의 메서드에 대해서도 @ExceptionHandler가 적용된다.
why? 컨트롤러 메서드에서 호출되기에 다른 클래스에서 예외가 발생하면 그것이 컨트롤러까지 전파되기 때문이다.)
아래는 내가 작성한 글로벌 예외 처리 과정을 작성하겠다.
@Getter
@RequiredArgsConstructor
public enum ErrorCode {
// 인증이 되어 있지 않을 때.
UNAUTHORIZED(401, "접근 권한이 없습니다."),
// 존재하지 않는 값을 보낼 때.
USER_NOT_FOUND(404, "존재하지 않는 회원입니다."),
POST_NOT_FOUND(404, "존재하지 않는 글입니다."),
private final int status;
private final String message;
}
몇가지만 가지고 왔고, UNAUTHORIZED 예외처리는 다음 글과 관련있기 때문에 남겨두었다.
enum class를 이용하여
첫번째 인자에는 http 상태코드
두번째 인자에는 에러에 대한 메시지를 담아두었다.
@Getter
@AllArgsConstructor
public class ErrorDto {
private final int status;
private final String message;
}
에러 내용을 담기 위해서 작성하였다.
@Getter
@AllArgsConstructor
public class CustomException extends RuntimeException {
private final ErrorCode errorCode;
}
RuntimeException을 상속하여 CustomException에서 사용할 수 있도록 하였다.
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/* CustomHandler 에러 처리 */
@ExceptionHandler(CustomException.class)
protected ResponseEntity<?> customExceptionHandler(CustomException ex) {
ErrorCode errorCode = ex.getErrorCode();
ErrorDto errorDto = new ErrorDto(errorCode.getStatus(), errorCode.getMessage());
log.error("Error occurred : {}, Stack trace: {}", ex.getMessage(), getCustomStackTrace(ex));
return new ResponseEntity<>(errorDto, HttpStatusCode.valueOf(errorDto.getStatus()));
}
/* MethodArgumentNotValidException 처리 */
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<Map<String, String>> handleValidationException(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
for (FieldError error : ex.getBindingResult().getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
}
log.error("Error occurred : {}", errors);
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
/* 일반 예외 처리 */
@ExceptionHandler
protected ResponseEntity<?> customServerException(Exception ex) {
ErrorDto error = new ErrorDto(INTERNAL_SERVER_ERROR.getStatus(), INTERNAL_SERVER_ERROR.getMessage());
log.error("Error occurred : {}, Stack trace: {}", ex.getMessage(), getCustomStackTrace(ex));
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
우선 class에 어노테이션을 달아줘야 하는데 2가지 어노테이션이 있다.
나는 RESTful한 @RestControllerAdvice를 작성하였다.
그리고, 예외 처리를 할 메서드에는 @ExceptionHandler를 추가해주어야 하고, 위 예시처럼 @ExceptionHandler(CustomException.class)이렇게 명시적으로 예외 처리 할 class를 지정해줘도 되지만, 안해줘도 된다.
다음으로 예외 처리를 위해서 메서드를 작성하여야 한다.
나는 3가지 예외 처리 메서드를 작성하였다.
첫 번째 메서드는 내가 작성한 CustomException과 관련된 예외 처리이다.
두 번째 메서드는 유효성 검사 중 오류가 발생한 부분에 대해서 예외를 처리해주는 것이다. (아래에 자세히 적어두겠다.)
세 번째 메서드는 작성하지 못한 예외 문구 or 잘못된 api 연결 등에 대해서 처리해준다.
두 번째 예외 처리 메서드는 다음과 같이 작동한다.
<SignUpDto에 대한 유효성 검사 문구들>
<Controller에서 SignUpDto에 대해서 유효성 검사를 위해 @Valid 어노테이션 추가>
<조건 만족 못할 시 반환값>
위와 같이 설정하면 아래와 같이 예외 문구를 처리할 수 있다. (첫 번째 예외 처리 메서드 문구이다.)
User foundUser = userRepository.findByUsername(username).orElseThrow(() ->
new CustomException(ErrorCode.USER_NOT_FOUND));
개인 프로젝트에서도 글로벌 예외 처리는 편리하고 중요하겠지만, 팀 프로젝트를 할 때 특히나 중요하다고 느꼈다. 사람마다 생각하는 예외 처리 문구가 다를 수 있고, 보내는 상태코드 값이 다를 수 있기 때문에 일관성이 없어질 수 있다.
팀원과 처음에 시작할 때 이것을 고려하면 예외 처리에 대한 일관성을 유지할 수 있다고 생각한다.