저번 포스팅에서 ExceptionHandler를 배워봤는데 SpringBoot에서 제공하는 방법을 사용해봅시다.
Spring에서는 ExceptionHandlerResolver를 아래 순서로 진행합니다.
HandlerExceptionResolverComposite 에 다음 순서로 등록
1. ExceptionHandlerExceptionResolver
2. ResponseStatusExceptionResolver
3. DefaultHandlerExceptionResolver
우선 순위가 가장 낮다
각각 어떤 차이가 있을까요?
HTTP 상태 코드를 지정해준다.
예) @ResponseStatus(value = HttpStatus.NOT_FOUND)
@ResponseStatus
가 달려있는 예외 처리@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
public class BadRequestException extends RuntimeException {
}
BadRequestException 예외가 컨트롤러 밖으로 넘어가면 ResponseStatusExceptionResolver 예외가
해당 애노테이션을 확인해서 오류 코드를 HttpStatus.BAD_REQUEST (400)으로 변경하고, 메시지도
담아요.
하지만 이방법은 내부 코드중에 response.sendError(statusCode, resolveReaseon)
를 호출하기 떄문에 WAS에서 다시 /error 페이지를 내부 요청한다는 단점..
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "error.bad")
public class BadRequestException extends RuntimeException {
}
이런식으로 메시지기능 MessageSource에서 찾는 기능도 제공합니다!
ResponseStatusException
예외public String responseStatusEx2() {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", newIllegalArgumentException());
}
이렇게 ResponseStatusException을 동적으로 에러발생 조건이 바뀌는 상황에서는 이런식으로 사용할 수 있습니다.
스프링 내부 기본 예외를 처리
대표적으로 파라미터 바인딩 시점에 타입이 맞지 않으면 내부에서 TypeMismatchException 이 발생하는데, 이 경우 예외가 발생했기 때문에 그냥 두면 서블릿 컨테이너까지 오류가 올라가고, 결과적으로 500
오류가 발생한다.
그런데 파라미터 바인딩은 대부분 클라이언트가 HTTP 요청 정보를 잘못 호출해서 발생하는 문제라서, HTTP 에서는 이런 경우 HTTP 상태 코드 400을 사용하도록 되어 있다. DefaultHandlerExceptionResolver 는 이것을 500 오류가 아니라 HTTP 상태 코드 400 오류로 sendError를 사용해 변경한다.
@ExceptionHandler 을 처리한다. API 예외 처리는 대부분 이 기능으로 해결한다.
Exception 우선순위중 가장 높아 먼저 처리한다. 그래서 실무 API 예외처리는 대부분 이 기능을 씁니다.
@ExceptionHandler
예외 처리 방법@ExceptionHandler
애노테이션을 선언하고, 해당 컨트롤러에서 처리하고 싶은 예외를 지정해주면 된다.
해당 컨트롤러에서 예외가 발생하면 이 메서드가 호출된다. 참고로 지정한 예외 또는 그 예외의 자식
클래스는 모두 잡을 수 있다.
이때 Exception의 예외처리 순서는 구체적인 것이 먼저 호출됩니다
부모, 자식 이렇게 호출되면 자식예외처리가 호출됩니다.
@ExceptionHandler({AException.class, BException.class})
public String ex(Exception e) {
log.info("exception e", e);
}
1) IllegalException
아래 코드가 호출되었다고 생각해봅시다.
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandle(Exception e) {
log.error("[exceptionHandle] ex", e);
return new ErrorResult("EX", "내부 오류");
}
2) 사용자정의 Exception을 ExceptionHandler( 내부 옵션을 지정하지 않은 경우 )
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandle(UserException e) {
log.error("[exceptionHandle] ex", e);
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
ResponseStatusException
을 던져라!위에서 한걸 ExceptionHandler를 Global하게 적용시키거나 한 파일에서 공통된 예외처리를 관리하는 방법입니다.
rollerAdvice 는 대상으로 지정한 여러 컨트롤러에 @ExceptionHandler , @InitBinder 기능을
부여해주는 역할.
@ControllerAdvice 에 대상을 지정하지 않으면 모든 컨트롤러에 적용된다. (글로벌 적용)
@RestControllerAdvice 는 @ControllerAdvice 와 같고, @ResponseBody 가 추가되어 있다는 차이정도.
@Controller , @RestController 의 차이와 같습니다.
특정 패키지, 특정 클래스, 특정 어노테이션에만 ControllerAdvice를 적용시키고 싶으면 아래처럼 선언해주면 끝!
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class,
AbstractController.class})
public class ExampleAdvice3 {}
@ExceptionHandler 와 @ControllerAdvice 를 조합하면 예외를 깔끔하게 해결할 수 있다.