Search
Duplicate
📒

[Spring Study] 07-2. 스프링 예외처리(HandlerExceptionResolver)

상태
완료
수업
Spring Study
주제
Exception
4 more properties
참고

스프링이 제공하는 예외처리

NOTE
Java에서 기본적인 예외처리는 try-catch이지만, Spring은 에러 처리라는 공통 관심사를 메인 로직으로부터 분리하는 예외 처리 방식인 HandlerExceptionResolver 인터페이스를 만들었습니다.
HandlerExceptionResolver는 대부분 발생한 Exception을 잡아내고, HTTP 상태나 응답 메시지를 설정합니다. 이로 인해, 웹 애플리케이션 서버(WAS)는 해당 요청을 정상적인 응답으로 인식하게 되고, 복잡한 WAS의 에러 전달이 발생하지 않습니다.
public interface HandlerExceptionResolver { ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); }
Java
복사
HandlerExceptionResolver
'handler'라는 Object 타입은 예외가 발생한 컨트롤러 객체를 가리킵니다. 예외가 발생하면, 디스패처 서블릿까지 전달되며, 적절한 예외 처리를 위한 HandlerExceptionResolver 구현체들이 빈으로 등록되어 관리됩니다.
ExceptionHandler, ResponseStatus, DefaultHandler 순서의 우선순위를 가지게 됩니다.
적용 가능한 구현체를 찾아 예외 처리를 하게 되는데, 대표적으로 4가지가 있습니다:
1.
DefaultErrorAttribute: 에러 속성을 저장하나, 직접 예외를 처리하지는 않습니다.
2.
ExceptionHandlerExceptionResolver: ControllerControllerAdvice에 있는 ExceptionHandler를 통해 에러 응답을 처리합니다.
3.
ResponseStatusExceptionResolver: HTTP 상태 코드를 지정하는 @ResponseStatus 또는 ResponseStatusException을 처리합니다.
4.
DefaultHandlerExceptionResovler: 스프링 내부에서 발생하는 기본 예외들을 처리합니다.
Spring은 다음과 같은 도구들로 ExceptionResolver를 동작시켜 에러를 처리할 수 있습니다.
1.
@ResponseStatus
2.
ResponseStatusException
3.
@ExceptionHandler
4.
@ControllerAdivce, @RestControllerAdvice

HandlerExceptionResolver 생성

NOTE
HandlerExceptionResolver는 API에서 발생하는 예외를 효과적으로 관리할 수 있습니다. 이를 통해 다른 HTTP 상태 코드를 지정하거나 사용자 정의 오류 메시지를 전달할 수 있습니다.
@GetMapping("/java-error") public ResponseEntity<String> getError(){ throw new RuntimeException(); }
Java
복사
@Slf4j public class MyHandlerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // RuntimeException 예외 발생시 200으로 상태코드 변경(원래는 500) if (ex instanceof RuntimeException) { log.info("RuntimeException을 200코드로 변경"); response.setStatus(HttpServletResponse.SC_OK); return new ModelAndView(); } return null; } }
Java
복사
HandlerExceptionResolver 구현
@Configuration public class WebConfig implements WebMvcConfigurer { // ExceptionHandler 등록 @Override public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { resolvers.add(new MyHandlerExceptionResolver()); } }
Java
복사
ExcetpionResolver 등록
configureHandlerExceptionResolvers()는 스프링이 기본으로 제공하는 ExceptionResolver를 제거하므로, extendHandlerExceptionResolvers를 사용하는 것이 좋습니다.
실제로는 직접 구현하지 않고, 스프링이 제공하는 ExceptionResovlver를 주로 사용합니다.

@ResponseStatus

NOTE
@ResponseStatus는 특정 예외에 대해 직접 지정된 HTTP 상태코드로 응답할 수 있게 해줍니다.
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류") public class BadRequestException extends RuntimeException {}
Java
복사
1. @ResponseStatus 방식
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", new IllegalArgumentException());
Java
복사
2. ResponseStatusException 사용

ResponseStatusException

NOTE
@ResponseStatus를 붙여줄 수 없는 경우, ResponseStatusException을 활용해서 예외처리를 해줄 수 있습니다.
@GetMapping("/status-exception") public ResponseEntity<?> getStatusException() { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Item Not Found"); }
Java
복사
ResponseStatus와 동일하게 ResponseStatusExceptionResolver가 에러를 처리합니다.
하지만 여러 한계점들이 존재해 실제로 @ExceptionHandler가 가장 많이 쓰인다.
예외가 WAS까지 전달된다.
예외 처리 코드가 중복될 수 있다.

@ExceptionHandler

NOTE
ExceptionHandler는 특정 컨트롤러 내에서 발생할 수 있는 예외를 처리하기 위해 사용되며, 예외 처리로직을 컨트롤러내에서 직접 정의할 수 있습니다.
@RestController public class ExceptionHandlingController { // IllegalArgumentException 처리 @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); } // UserException 처리 @ExceptionHandler(UserException.class) public ResponseEntity<String> handleUserException(UserException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); } // 모든 Exception 처리 @ExceptionHandler(Exception.class) public ResponseEntity<String> handleException(Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred"); } @GetMapping("/test") public String test(@RequestParam(required = false) String param) { // 로직.. } }
Java
복사
@ControllerAdivce@Controller 어노테이션 내부의 메소드에 추가해야 동작합니다.
@ExceptionHandler에 등록된 예외 클래스와 파라미터로 받는 예외 클래스가 다른경우 컴파일 시점에 예외가 발생하지 않고, 런타임 시점에 예외가 발생한다.

@ControllerAdvice, @RestControllerAdvice

NOTE
@ControllerAdvice는 모든 컨트롤러에 걸쳐 공통의 예외 처리, 데이터 바인딩 설정 등을 적용할 수 있게 해주는 어노테이션입니다. 간단히 설명하면 컨트롤러와 예외를 분리시키기 위해 등장했다고 생각하면 됩니다.
@ControllerAdvice는 대상으로 지정한 여러 컨트롤러에 @ExceptionHandler, @InitBinder 기능을 부여해주는 역할을 합니다.
@RestControllerAdvice(basePackages = "hello.exception.api") public class ExControllerAdvice { @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptioHandler(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", "내부오류"); } }
Java
복사
ControllerAdvice를 통해 예외 Handler를 따로 분리할 수 있다!
@RestControllerAdvice@ControllerAdvice와 같고 @ResponseBody가 추가된 것입니다.
// @RestController 어노테이션 범위에 지정 @ControllerAdvice(annotations = RestController.class) public class ExampleAdvice1 {} // 특정 패키지 범위 @ControllerAdvice("org.example.controllers") public class ExampleAdvice2 {} // 특정 클래스 범위 @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) public class ExampleAdvice3 {}
Java
복사
대상을 지정하지 않으면 모든 컨트롤러에 적용됩니다.