[TIL] 23.01.23 @RestControllerAdvice 구현하기

hyewon jeong·2023년 1월 23일
0

TIL

목록 보기
75/138

1 . 문제점

예외처리에 대한 국한적인 처리방법이 아닌 전반적인 예외처리를 하기 위한 방법 모색


2 . 시도한 점

@ExeptionHandler와 RestConrollerAdvice 이용


3 . 해결

📌 프로젝트에 적용

적용 전

적용 후

커스텀한 예외처리를 동일하게 적용 할 수 있다.


4 . 알게 된점

ex.getMessage()에서는 전체 오류 메시지는 하나의 문자열로 반환시켜주고, ex.getBindingResult() 에서는 각각의 데이터가 바인딩 된 결과(Valiation 결과)를 하나씩 반환시켜줍니다. 아래 이미지는 name과 joinDate 2개의 필드에서 Validation 오류가 발생했을 때의 Debugging 화면 입니다.
https://cdn.inflearn.com/public/comments/1ca44507-6096-4708-bb94-aea3995840cd/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-05-14%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%209.40.30.png

https://cdn.inflearn.com/public/comments/d7120b47-3109-433c-bdf6-550dac1841d4/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-05-14%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%209.42.01.png

위에서 보시는 것처럼, ex.getBindingResult()에서는 2개의 에러가 발생하였다는 정보 뿐만아니라, Validation 체크를 했던 대상에 대한 정보와 같이 조금 더 자세한 정보를 확인해 보실 수 있습니다.


5 . 기타 공부

💡 에러코드 정의하기

클라이언트에게 보내줄 에러 코드를 정의해야 한다.에러 코드는 애플리케이션에서 전역적으로 사용되는 CommonErrorCode와 특정 도메인에 대해 구체적으로 내려가는 UserErrorCode로 나누고, 인터페이스를 이용해 추상화한다.

먼저 다음과 같이 CommonErrorCode와 UserErrorCode의

📌 1. 공통 메소드로 추상화할 인터페이스를 정의할 수 있다

@Getter
@AllArgsConstructor
public class ErrorDto {
    private final String message;
    private final int StatusCode;
}

📌 2. 그리고 발생할 수 있는 에러 코드를 정의한다.

@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;


}

400 에러 (클라이언트 측)

4xx 클래스의 상태 코드는 클라이언트에 오류가 있음을 나타낸다.

  • 400(잘못된 요청): 서버가 요청의 구문을 인식하지 못했다.

  • 401(권한 없음): 이 요청은 인증이 필요하다. 서버는 로그인이 필요한 페이지에 대해 이 요청을 제공할 수 있다. 상태 코드 이름이 권한 없음(Unauthorized)으로 되어 있지만 실제 뜻은 인증 안됨(Unauthenticated)에 더 가깝다
    -> 즉 401은 인증 실패
    예) 비밀번호가 일치하지 않습니다.

  • 402(결제 필요): 이 요청은 결제가 필요합니다.

  • 403(Forbidden, 금지됨): 서버가 요청을 거부하고 있다. 예를 들자면, 사용자가 리소스에 대한 필요 권한을 갖고 있지 않다.
    -> 즉 403은 인가 실패
    예) 접근할 수 있는 권한이 없습니다.

  • 404(Not Found, 찾을 수 없음): 서버가 요청한 페이지(Resource)를 찾을 수 없다. 예를 들어 서버에 존재하지 않는 페이지에 대한 요청이 있을 경우 서버는 이 코드를 제공한다.
    예) 사용자가 존재하지 않습니다.

  • 409(충돌): 서버가 요청을 수행하는 중에 충돌이 발생했다. 서버는 응답할 때 충돌에 대한 정보를 포함해야 한다. 서버는 PUT 요청과 충돌하는 PUT 요청에 대한 응답으로 이 코드를 요청 간 차이점 목록과 함께 표시해야 한다.

📌 3. 발생한 예외를 처리해 줄 예외 클래스(Exception Class)를 추가해주어야 한다.

  • 우리는 언체크 예외(런타임 예외)를 상속받는 예외 클래스를 추가해줄 수 있다.
@Getter
@RequiredArgsConstructor
public class CustomException extends RuntimeException{

    private final ExceptionStatus exceptionStatus;
}
  • 여기서 체크 예외가 아닌 언체크 예외를 상속받도록 한 이유가 있다. 왜냐하면 일반적인 비지니스 로직들은 따로 catch해서 처리할 것이 없므로 만약 체크 예외로 한다면 불필요하게 throws가 전파될 것이기 때문이다.
    (만약 체크 예외, 언체크 예외에 대해서 잘 모르면 여기를 참고해주세요!)
  • 또한 Spring은 내부적으로 발생한 예외를 확인하여 언체크 예외이거나 에러라면 자동으로 롤백시키도록 처리한다. Spring에서 체크 예외만 롤백을 안하는 이유는 체크 예외는 처리가 강제되기 때문에 개발자가 무언가를 처리할 것이라는 기대 때문이다.

  • 과거에는 체크 예외가 많이 사용되었지만, 최근에는 거의 모든 경우에 언체크 예외를 사용한다고 보면 된다.

💡 [ @RestControllerAdvice 구현하기 ]

이제 전역적으로 에러를 처리해주는 @RestControllerAdvice 클래스를 추가해주어야 한다.

클라이언트로 다음과 같은 포맷의 에러를 던져주도록 한다.

{
    "code": "401",
    "message": "비밀번호가 일치 하지 않습니다."
}

이를 위해 다음과 같인 에러 응답 클래스를 추가해줄 수 있다.

  • RestApiException 예외와 @Valid에 의한 유효성 검증에 실패했을 때 발생하는 IllegalArgumentException 예외와 마지막으로 잘못된 파라미터를 넘겼을 경우 발생하는 IllegalArgumentException 에러를 처리해주도록 하자.

(아래의 코드는 예시 코드이므로, 상황에 맞게 최적화 및 커스터마이징 해주도록 하자.)

@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는 매우 유연하게 에러를 처리할 수 있는 방법을 제공하는 기능이다. @ExceptionHandler는 다음에 어노테이션을 추가함으로써 에러를 손쉽게 처리할 수 있다.

컨트롤러의 메소드
@ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메소드

예를 들어 다음과 같이 컨트롤러의 메소드에 @ExceptionHandler를 추가함으로써 에러를 처리할 수 있다. @ExceptionHandler에 의해 발생한 예외는 ExceptionHandlerExceptionResolver에 의해 처리가 된다.

HttpStatus.valueOf : 지정된 숫자 값으로 HttpStatus enum 상수를 반환합니다.

참고
https://mangkyu.tistory.com/205

profile
개발자꿈나무

0개의 댓글