예외처리에 대한 국한적인 처리방법이 아닌 전반적인 예외처리를 하기 위한 방법 모색
@ExeptionHandler와 RestConrollerAdvice 이용
적용 전
적용 후
커스텀한 예외처리를 동일하게 적용 할 수 있다.
ex.getMessage()에서는 전체 오류 메시지는 하나의 문자열로 반환시켜주고, ex.getBindingResult() 에서는 각각의 데이터가 바인딩 된 결과(Valiation 결과)를 하나씩 반환시켜줍니다. 아래 이미지는 name과 joinDate 2개의 필드에서 Validation 오류가 발생했을 때의 Debugging 화면 입니다.
위에서 보시는 것처럼, ex.getBindingResult()에서는 2개의 에러가 발생하였다는 정보 뿐만아니라, Validation 체크를 했던 대상에 대한 정보와 같이 조금 더 자세한 정보를 확인해 보실 수 있습니다.
클라이언트에게 보내줄 에러 코드를 정의해야 한다.에러 코드는 애플리케이션에서 전역적으로 사용되는 CommonErrorCode와 특정 도메인에 대해 구체적으로 내려가는 UserErrorCode로 나누고, 인터페이스를 이용해 추상화한다.
먼저 다음과 같이 CommonErrorCode와 UserErrorCode의
@Getter
@AllArgsConstructor
public class ErrorDto {
private final String message;
private final int StatusCode;
}
@Getter
@RequiredArgsConstructor //블로그방식
//@AllArgsConstructor 팀원방식
public enum ExceptionStatus {
PASSWORDS_DO_NOT_MATCH(401,"비밀번호가 일치 하지 않습니다."),
INVALID_TOKEN(401,"유효하지 않은 토큰입니다."),
AUTHORIZATION_EXCEPTION(403,"접근할 수 있는 권한이 없습니다."),
POST_IS_EMPTY(404,"해당 게시글이 존재 하지 않습니다."),
COMMENT_IS_EMPTY(404,"해당 댓글이 존재 하지 않습니다."),
USER_IS_NOT_EXIST(404,"사용자가 존재 하지 않습니다."),
REQUEST_IS_EMPTY(404,"요청이 존재하지 않습니다."),
PAGE_IS_NOT_EXIST(404,"요청하신 페이지 내역이 존재하지 않습니다."),
USERNAME_IS_EXIST(409,"이미 등록된 username입니다.");
private final int statusCode;
private final String message;
}
4xx 클래스의 상태 코드는 클라이언트에 오류가 있음을 나타낸다.
400(잘못된 요청): 서버가 요청의 구문을 인식하지 못했다.
401(권한 없음): 이 요청은 인증이 필요하다. 서버는 로그인이 필요한 페이지에 대해 이 요청을 제공할 수 있다. 상태 코드 이름이 권한 없음(Unauthorized)으로 되어 있지만 실제 뜻은 인증 안됨(Unauthenticated)에 더 가깝다
-> 즉 401은 인증 실패
예) 비밀번호가 일치하지 않습니다.
402(결제 필요): 이 요청은 결제가 필요합니다.
403(Forbidden, 금지됨): 서버가 요청을 거부하고 있다. 예를 들자면, 사용자가 리소스에 대한 필요 권한을 갖고 있지 않다.
-> 즉 403은 인가 실패
예) 접근할 수 있는 권한이 없습니다.
404(Not Found, 찾을 수 없음): 서버가 요청한 페이지(Resource)를 찾을 수 없다. 예를 들어 서버에 존재하지 않는 페이지에 대한 요청이 있을 경우 서버는 이 코드를 제공한다.
예) 사용자가 존재하지 않습니다.
409(충돌): 서버가 요청을 수행하는 중에 충돌이 발생했다. 서버는 응답할 때 충돌에 대한 정보를 포함해야 한다. 서버는 PUT 요청과 충돌하는 PUT 요청에 대한 응답으로 이 코드를 요청 간 차이점 목록과 함께 표시해야 한다.
@Getter
@RequiredArgsConstructor
public class CustomException extends RuntimeException{
private final ExceptionStatus exceptionStatus;
}
또한 Spring은 내부적으로 발생한 예외를 확인하여 언체크 예외이거나 에러라면 자동으로 롤백시키도록 처리한다. Spring에서 체크 예외만 롤백을 안하는 이유는 체크 예외는 처리가 강제되기 때문에 개발자가 무언가를 처리할 것이라는 기대 때문이다.
과거에는 체크 예외가 많이 사용되었지만, 최근에는 거의 모든 경우에 언체크 예외를 사용한다고 보면 된다.
이제 전역적으로 에러를 처리해주는 @RestControllerAdvice 클래스를 추가해주어야 한다.
클라이언트로 다음과 같은 포맷의 에러를 던져주도록 한다.
{
"code": "401",
"message": "비밀번호가 일치 하지 않습니다."
}
이를 위해 다음과 같인 에러 응답 클래스를 추가해줄 수 있다.
(아래의 코드는 예시 코드이므로, 상황에 맞게 최적화 및 커스터마이징 해주도록 하자.)
@RestControllerAdvice //전역에 예외처리하는 어노테이션
public class ExceptionAdviceHandler {
@ExceptionHandler({CustomException.class})
protected ResponseEntity handleCustomException(CustomException ex) {
return new ResponseEntity(new ErrorDto(ex.getExceptionStatus().getStatusCode(), ex.getExceptionStatus().getMessage()), HttpStatus.valueOf(ex.getExceptionStatus().getStatusCode()));
}
@ExceptionHandler({MethodArgumentNotValidException.class})
protected ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return new ResponseEntity<>(e.getBindingResult().getFieldErrors().get(0),
// .get(0).getField() + "가 "+ e.getBindingResult().getFieldErrors().get(0).getDefaultMessage(),
HttpStatus.BAD_REQUEST);
}
@ExceptionHandler({RuntimeException.class})
protected ResponseEntity<String> handlerEtcException(RuntimeException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
}
@ExceptionHandler는 매우 유연하게 에러를 처리할 수 있는 방법을 제공하는 기능이다. @ExceptionHandler는 다음에 어노테이션을 추가함으로써 에러를 손쉽게 처리할 수 있다.
컨트롤러의 메소드
@ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메소드
예를 들어 다음과 같이 컨트롤러의 메소드에 @ExceptionHandler를 추가함으로써 에러를 처리할 수 있다. @ExceptionHandler에 의해 발생한 예외는 ExceptionHandlerExceptionResolver에 의해 처리가 된다.
HttpStatus.valueOf : 지정된 숫자 값으로 HttpStatus enum 상수를 반환합니다.