[2/2 : Exception다루기] ExceptionHandler가 뭘까?

BlackBean99·2022년 9월 9일
1

SpringBoot

목록 보기
15/20
post-thumbnail
post-custom-banner

저번 포스팅에서 ExceptionHandler를 배워봤는데 SpringBoot에서 제공하는 방법을 사용해봅시다.

Spring에서는 ExceptionHandlerResolver를 아래 순서로 진행합니다.

HandlerExceptionResolverComposite 에 다음 순서로 등록
1. ExceptionHandlerExceptionResolver
2. ResponseStatusExceptionResolver
3. DefaultHandlerExceptionResolver
우선 순위가 가장 낮다

각각 어떤 차이가 있을까요?


1. ResponseStatusExceptionResolver

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을 동적으로 에러발생 조건이 바뀌는 상황에서는 이런식으로 사용할 수 있습니다.


2. DefaultHandlerExceptionResolver

스프링 내부 기본 예외를 처리

대표적으로 파라미터 바인딩 시점에 타입이 맞지 않으면 내부에서 TypeMismatchException 이 발생하는데, 이 경우 예외가 발생했기 때문에 그냥 두면 서블릿 컨테이너까지 오류가 올라가고, 결과적으로 500 오류가 발생한다.
그런데 파라미터 바인딩은 대부분 클라이언트가 HTTP 요청 정보를 잘못 호출해서 발생하는 문제라서, HTTP 에서는 이런 경우 HTTP 상태 코드 400을 사용하도록 되어 있다. DefaultHandlerExceptionResolver 는 이것을 500 오류가 아니라 HTTP 상태 코드 400 오류로 sendError를 사용해 변경한다.


3. (중요!) ExceptionHandlerExceptionResolver

@ExceptionHandler 을 처리한다. API 예외 처리는 대부분 이 기능으로 해결한다.
Exception 우선순위중 가장 높아 먼저 처리한다. 그래서 실무 API 예외처리는 대부분 이 기능을 씁니다.

@ExceptionHandler 예외 처리 방법

@ExceptionHandler 애노테이션을 선언하고, 해당 컨트롤러에서 처리하고 싶은 예외를 지정해주면 된다.
해당 컨트롤러에서 예외가 발생하면 이 메서드가 호출된다. 참고로 지정한 예외 또는 그 예외의 자식
클래스는 모두 잡을 수 있다.

이때 Exception의 예외처리 순서는 구체적인 것이 먼저 호출됩니다
부모, 자식 이렇게 호출되면 자식예외처리가 호출됩니다.

  • Tip ) 한번에 여러개 적용 가능
@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", "내부 오류");
}
  1. 컨트롤러를 호출한 결과 IllegalArgumentException 예외가 컨트롤러 밖으로 던져진다.
  2. 예외가 발생했으로 ExceptionResolver 가 작동한다.
  3. 가장 우선순위가 높은 ExceptionHandlerExceptionResolver 가 실행된다.
  4. xceptionHandlerExceptionResolver 는 해당 컨트롤러에 IllegalArgumentException 을 처리할 수 있는 @ExceptionHandler 가 있는지 확인한다.
  5. illegalExHandle() 를 실행한다. @RestController 이므로 illegalExHandle() 에도 @ResponseBody 가 적용된다. 따라서 HTTP 컨버터가 사용되고, 응답이 다음과 같은 JSON으로 반환된다.
  6. @ResponseStatus(HttpStatus.BAD_REQUEST) 를 지정했으므로 HTTP 상태 코드 400으로 응답한다.

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);
}
  1. @ExceptionHandler 에 예외를 지정하지 않으면 해당 메서드 파라미터 예외를 사용한다. 여기서는 UserException 을 사용한다.
  2. ResponseEntity 를 사용해서 HTTP 메시지 바디에 직접 응답한다. 물론 HTTP 컨버터가 사용된다.
  3. ResponseEntity 를 사용하면 HTTP 응답 코드를 프로그래밍해서 동적으로 변경할 수 있다. 앞서 살펴본 @ResponseStatus 는 어노테이션이므로 HTTP 응답 코드를 동적으로 변경할 수 없다 하려면 아까 말했듯이 ResponseStatusException 을 던져라!

@ControllerAdvice

위에서 한걸 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 를 조합하면 예외를 깔끔하게 해결할 수 있다.

Reference

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-controller-advice

profile
like_learning
post-custom-banner

0개의 댓글