ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 API 예외처리 - @ExceptionHandler (대부분 쓰임)
    스프링/스프링 MVC 패턴 2023. 3. 5. 15:41

    API 예외처리의 어려운 점

    - 특정 컨트롤러에서만 발생하는 예외를 처리하기 어렵다.

    - 회원 처리하는 컨트롤러에서 발생한 RuntimeException 예외와 상품을 관리하는 컨트롤러에서 발생한 RuntimeException 예외를 서로 다른 방식으로 처리하고 싶다면??

     

    이러한 단점들을 보완하기 위해서 @ExceptionHanlder를 사용하면 된다.

     

    @Slf4j
    @RestController
    public class ApiExceptionV2Controller {
    
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(IllegalArgumentException.class)
        public ErrorResult illegalExHandler(IllegalArgumentException e) {
            log.error("[exceptionHandler] ex", e);
            return new ErrorResult("BAD", e.getMessage());
        }
    
        @GetMapping("/api2/members/{id}")
        public MemberDto getMember(@PathVariable("id") String id) {
    
            if (id.equals("ex")) {
                throw new RuntimeException("잘못된 사용자");
            }
            if (id.equals("bad")) {
                throw new IllegalArgumentException("잘못된 입력 값");
            }
            if (id.equals("user-ex")) {
                throw new UserException("사용자 오류");
            }
    
            return new MemberDto(id, "hello " + id);
        }
    
        @Data
        @AllArgsConstructor
        static class MemberDto {
            private String memberId;
            private String name;
        }
    }

    위의 ExceptionHandler 애노테이션을 제외한 메서드들은 이전의 코드를 복사해 온 것이다.

    @ExceptionHanldler 애노테이션을 사용하면 해당 에러를 잡아서 예외를 처리하게 된다. 하지만 200 Status로 예외 없이 처리되기 때문에 @ResponseStatus로 400 에러가 나오게 만들어줬다.

     

     

     

    @ExceptionHandler // 여기 안하고 메서드 파라미터에다가 줘도 됨
    public ResponseEntity<ErrorResult> userExHandler(UserException e) {
        log.error("[exceptionHandler] ex", e);
        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
        return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
    }

    UserException 예외를 처리하는 방식이다. 애노테이션의 정보를 생략하고 메서드의 파라미터에다가 해당 예외 상태를 넣어두었다.

     

     

     

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandler(Exception e) {
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("EX", "내부 오류");
    }

    Exception은 모든 예외 처리의 부모 클래스이다. 위의 2개는 이미 예외 처리가 있어서 2개의 예외는 저 코드가 실행되게 된다. 하지만 저 2개의 예외를 제외하고 처리하지 못한 예외가 발생했을 때에는 Exception이라는 루트 클래스의 에러가 처리하게 된다.

     

     

     

    스프링의 우선순위는 항상 자세한 것이 우선순위를 가지기 때문에 부모, 자식 클래스가 다 있으면 자식예외 클래스가 우선으로 처리된다.

     

     

     

     

     

    API 예외처리 - @ControllerAdvice (정상, 예외 코드의 분리)

    컨트롤러에서 예외 코드만 따로 분리해서 새로운 클래스를 만들었다.

    @Slf4j
    @RestControllerAdvice
    public class ExControllerAdvice {
    
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(IllegalArgumentException.class)
        public ErrorResult illegalExHandler(IllegalArgumentException e) {
            log.error("[exceptionHandler] ex", e);
            return new ErrorResult("BAD", e.getMessage());
        }
    
        @ExceptionHandler // 여기 안하고 메서드 파라미터에다가 줘도 됨
        public ResponseEntity<ErrorResult> userExHandler(UserException e) {
            log.error("[exceptionHandler] ex", e);
            ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
            return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
        }
    
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        @ExceptionHandler
        public ErrorResult exHandler(Exception e) {
            log.error("[exceptionHandler] ex", e);
            return new ErrorResult("EX", "내부 오류");
        }
    }

     

    이전 코드에서 위의 코드를 가져오고, 이전 코드에서는 위와 같은 에러 코드는 삭제했다.

     

    정상 코드와 에러 코드가 붙어있으면 혼란을 줄 수 있기 때문에 위와 같이 정상 코드와 에러 코드 분리를 통해 코드가 더 간결해졌다.

     

     

    @ControllerAdvice 라고만 쓰면 모든 컨트롤러에서 전부 사용 가능하다.

    @ControllerAdvice(annotaions = RestController.class) - RestController에서만 사용가능

    @ControllerAdvice("org.example.controllers") - 특정 패키지에서만 사용가능

    @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) - 특정 클래스에서만 사용가능

     

     

    보통 3번째 줄을 많이 사용한다고 한다.

Designed by Tistory.