ExceptionResolver
spring(WAS) 내 예외 처리
BasicErrorController
spring 기본 에러 처리 (페이지 기반)
스프링부트는 예외가 발생하면 기본적으로 `/error`로 에러 요청을 다시 전달하도록 WAS 설정을 해두었다고 한다.
@RequsetMapping에 /error로 매핑된걸 볼 수 있다. 참고로 에러 경로는 properties에서 server.error.path로 변결 가능하다
별도의 설정이 없다면 예외 발생시에 BasicErrorController로 에러 처리 요청이 전달된다.
HandlerExceptionResolver
Java에서는 예외 처리를 위하 try-catch를 사용해야하지만 try-catch를 모든 코드에 붙이는 것은 비효율적임
스프링은 예외 처리를 위한 try-catch를 사용하지 않고, 고통 관심사(cross-cutting concerns)를 메인 로직에서 분리하였다.
그 결과가 예외 처리 전략을 추상화한 HandlerExceptionResolver 인터페이스가 나왔다.
대부분의 HandlerExceptionResolver는 발생한 Exception을 catch하고 HTTP 상태나 응답 메세지 등을 설정한다. 그래서 WAS는 해당 요청이 정상적인 응답인 것으로 인식되며, BaseErrorController를 호출하지 않는다.
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
HandlerExceptionResolver는 예외가 발생한 컨트롤러 객체이다. 예외가 던져지면 디스패처 서블릿까지만 전달되는데, 적합한 예외 처리를 위해 HandlerExceptionResolver 구현체들을 빈으로 등록해서 관리한다.
그리고 적용 가능한 구현체를 찾아 예외 처리를 하고, 우선순위대로 아래 4가지 구현체들이 빈으로 등록되어 있다.
- DefaultErrorAttributes: 에러 속성을 저장하며 직접 예외를 처리하지 않는다.
- ExceptionHandlerExceptionResolver: ExceptionHandler 처리
- ResponseStatusExceptionResolver: ResponseStatus 또는 ResponseStatusException
- DefaultHandlerExceptionResolver: 스프링 내부의 기본 예외를 처리
위 순서대로 ExceptionResover 들을 순회하며 처리하고,
적합한 ExceptionResovler 가 없으면 마지막으로는 예외가 서블릿까지 전달되고,
서블릿은 Spring Boot가 진행한 자동 설정에 맞게 BasicErrorController 로 요청을 다시 전달한다.
DefaultErrorAttributes
직접 예외를 처리하지 않고 속성만 관리.
내부적으로 DefaultErrorAttributes를 제외하고 직접 예외를 처리하는 3가지 ExceptionResovler들을 HandlerExceptionResolvercComposite로 모아서 관리한다.
컴포지트 패턴을 적용해서 실제로 예외 처리기들을 따로 관리하는 것이다.
ResponseStatus / RespnseStatusException / ExceptionHandler
위 세가지로 ExceptionResolver를 동작시켜 에러를 처리할 수 있다.
@ResponseStatus
ResponseStatusExceptionResolver에서 처리한다.
HTTP 결과 반환 상태 변경하도록 도와주는 어노테이션
다음과 같은 경우들에 적용할 수 있다.
- Exception 클래스 자체
- 메소드에 @ExceptionHandler 와 함께
- 클래스에 @ControllerAdvice / @RestControllerAdvice와 함께
@ResponseStatus의 단점
- 응답 내용(Payload) 수정 불가능
- 예외 클래스 단위로 상태를 정의하여, 예외 클래스 내 세부적으로 HTTP 상태 분기 불가능
- 같은 예외 클래스 내 별도의 응답 상태가 필요하다면 예외 클래스를 그 수에 맞춰 추가해야됨.
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NoSuchElementFoundException extends RuntimeException {
...
}
ResponseStatusException
ResponseStatusExceptionResolver에서 처리한다.
예외 발생과 동시에 예외에 해당하는 에러 HTTP 상태를 동시에 정의
@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");
}
}
HttpStatus와 함께 선택적으로 reason과 cause를 추가할 수 있고, 언체크 예외 상속받고 있어 명시적으로 에러를 처리해 주지 않아도 된다.
ResponseStatusException의 단점
- 직접 예외 처리를 프로그래밍하므로 일관된 예외 처리가 어려움
- 예외 처리 코드가 중복될 수 있음
- Spring 내부의 예외를 처리하는 것이 어려움
- 예외가 WAS까지 전달되고, WAS의 에러 요청 전달이 진행됨.
ExceptionHandler
예외에 따라 세부 처리 = 전체 방법론 중에 가장 유연한 예외 처리 방식
- Controller 내 메서드 부착
- ControllerAdvice, RestControllerAdvice 내 메서드에 부착
@ExceptionHandler(NoSuchElementFoundException.class)
public ResponseEntity<String> handleNoSuchElementFoundException(NoSuchElementFoundException exception) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
}
@ExecptionHandler는 Exception 클래스들을 속성으로 받아 처리할 예외를 지정할 수 있다.
만약 ExceptionHanlder 어노테이션에 예외 클래스를 지정하지 않는다면, 파라미터에 설정된 에러 클래스를 처리하게 된다.
@ResponseStatus와도 결합 가능한데, 만약 ResponseEntity에도 status를 지정하고 @ResponseStatus도 있다면 ResponseEntity가 우선순위를 갖는다.
@Controller 의 Exception 처리를 ControllerAdvice로 책임 위임한 실습 코드
@ControllrAdvice만 사용하면 500, BaseRespnse미반환하는 문제 발생
- 메소드마다 @ResponseBody 붙이면 해결 가능
- @RestControllerAdvice를 클래스 레벨에 붙여서 해결 가능 (@ControllerAdvice + @ResponseBody)
'ASAC 웹 풀스택 > Spring Boot' 카테고리의 다른 글
Spring Best Practices(1) - JSON 관련 어노테이션 (0) | 2024.10.11 |
---|---|
Spring 구조(2) - 3-Layerd 아키텍쳐 패턴 (0) | 2024.10.08 |
Spring 구조(1) - MVC 아키텍처 패턴, Front Controller (1) | 2024.10.08 |
Spring Boot 특장점 및 동작(1) - Spring과 Spring Boot의 차이점 (0) | 2024.10.08 |
Spring Bean 원리(1) - 싱글톤, IoC, Bean 등록/사용 (3) | 2024.10.08 |