-
스프링 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번째 줄을 많이 사용한다고 한다.
'스프링 > 스프링 MVC 패턴' 카테고리의 다른 글
포맷터 - Formatter (0) 2023.03.07 스프링 타입 컨버터 (0) 2023.03.07 API 예외처리 - 스프링 ExceptionResolver (0) 2023.03.05 API 예외처리 - HandlerExceptionResolver (0) 2023.03.05 스프링 부트의 오류 페이지 (0) 2023.03.03