Exception 처리(2)
👉 스프링이 제공하는 다양한 예외처리 방법
# Spring은 에러 처리라는 공통 관심사(cross-cutting concerns)를 메인 로직으로부터 분리하여 예외 처리 전략을 추상화한 HandlerExceptionResolver 인터페이스를 만들었다.
// Object 타입인 handler는 예외가 발생한 컨트롤러 객체
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex);
}
- 발생 위치 : 시스템 레벨에서 생성
- 발생 클래스 : java.lang.Error 클래스의 서브 클래스
# HandlerExceptionResolver가 빈으로 등록해서 관리하는 4가지 구현체
- DefaultErrorAttributes : 에러 속성을 저장하며 직접 예외를 처리하지는 않는다.
- ExceptionHandlerExceptionResolver : 에러 응답을 위한 Controller나 ControllerAdvice에 있는 ExceptionHandler를 처리함
- ResponseStatusExceptionResolver : Http 상태 코드를 지정하는 @ResponseStatus 또는 ResponseStatusException를 처리함
- DefaultHandlerExceptionResolver : 스프링 내부의 기본 예외들을 처리.
# Spring은 아래와 같은 도구들로 ExceptionResolver를 동작시켜 에러를 처리할 수 있다.
- @ResponseStatus :
에러 HTTP 상태를 변경하도록 도와주는 어노테이션
- Exception 클래스 자체
- 메소드에 @ExceptionHandler와 함께
- 클래스에 @RestControllerAdvice와 함께
// 이 @ResponseStatus로 응답 상태를 지정해줄 수 있다.
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NoSuchElementFoundException extends RuntimeException {
...
}
// 그러면 ResponseStatusExceptionResolver가 지정해준 상태로 에러 응답이 내려가도록 처리한다.
{
"timestamp": "2021-12-31T03:35:44.675+00:00",
"status": 404,
"error": "Not Found",
"path": "/product/5000"
}
- ResponseStatusException :
HttpStatus와 함께 선택적으로 reason과 cause를 추가할 수 있고, 언체크 예외을 상속받고 있어 명시적으로 에러를 처리해주지 않아도 된다.
@GetMapping("/product/{id}")
public ResponseEntity<Product> getProduct(@PathVariable String id) {
try {
return ResponseEntity.ok(productService.getProduct(id));
} catch (NoSuchElementFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Item Not Found");
}
}
- @ExceptionHandler :
다음에 어노테이션을 추가함으로써 에러를 손쉽게 처리.
- 컨트롤러의 메소드
- @ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메소드
// 컨트롤러의 메소드에 @ExceptionHandler를 추가함으로써 에러를 처리.
// @ExceptionHandler에 의해 발생한 예외는 ExceptionHandlerExceptionResolver에 의해 처리.
@RestController
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping("/product/{id}")
public Response getProduct(@PathVariable String id){
return productService.getProduct(id);
}
@ExceptionHandler(NoSuchElementFoundException.class)
public ResponseEntity<String> handleNoSuchElementFoundException(NoSuchElementFoundException exception) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
}
}
- @ControllerAdvice와 @RestControllerAdvice :
다음에 어노테이션을 추가함으로써 에러를 손쉽게 처리.
- 컨트롤러의 메소드
- @ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메소드
// @ControllerAdvice는 여러 컨트롤러에 대해 전역적으로 ExceptionHandler를 적용
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
...
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
// @Component 어노테이션이 있어서 @ControllerAdvice가 선언된 클래스는 스프링 빈으로 등록
public @interface ControllerAdvice {
...
}
// 특정 클래스에만 제한적으로 적용하고 싶다면 @RestControllerAdvice의 basePackages 등을 설정함으로써 제한할 수 있다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoSuchElementFoundException.class)
protected ResponseEntity<?> handleNoSuchElementFoundException(NoSuchElementFoundException e) {
final ErrorResponse errorResponse = ErrorResponse.builder()
.code("Item Not Found")
.message(e.getMessage()).build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
}
🙋♂️ 소감 :
예외가 발생하여도 정상적인 흐름이 되어야 하기 때문에 예외 처리 작업은 정말 중요한 작업이다.
자바는 안전성이 중요한 언어로 대부분 프로그램에서 발생하는 오류에 대해 문법적으로 예외 처리를 해야 하고,
오류가 발생 했을 때 그 오류에 대한 기록을 남겨 디버깅에 용이하기 위해 충분한 로그를 남겨야 한다.
😈 아는 내용이라고 그냥 넘어가지 않기! 😈
'❤️🔥TIL (Today I Learned)' 카테고리의 다른 글
[TIL] 2023-01-12(54day) / SQL JOIN / 제약조건 (0) | 2023.01.12 |
---|---|
[TIL] 2023-01-11(53day) / SQL 데이터 조회 / 변경 (0) | 2023.01.11 |
[TIL] 2023-01-09(51day) (0) | 2023.01.09 |
[TIL] 2023-01-06(50day) (0) | 2023.01.06 |
[TIL] 2023-01-05(49day) (0) | 2023.01.06 |
댓글