WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
스프링의 처리과정을 보면 예외가 발생하는 부분은 크게 두가지로 나눌 수 있다.
스프링 MVC에서 에러의 대다수는 DispatcherServlet
에서 발생한다. HandlerExceptionResolver
를 사용하면 예외를 내부 (Servlet Container 내부)에서 자체적으로 해결할 수 있다.
클라이언트의 요청을 DispatcherServlet
밖에서 처리하는 도중 예외가 발생하면 DispatcherServlet
이 예외를 처리해줄 수 없다. 즉, HandlerExceptionResolver
의 처리를 받을 수 없다.
처리 못하는 이유는 DispatcherServlet
에서 처리하기도 전에 예외가 발생되기 때문이다.
이렇게 Filter
에서 예외가 발생하면 Web Application
레벨에서 처리를 해줘야 한다.
Filter에서 예외가 발생한다면?
web.xml에 error-page를 잘 등록해줘서 에러를 사용자에게 응답
Filter내부에서 예외를 처리하기 위한 필터를 따로 둬서 try-catch문을 사용하여 예외 처리
ExceptionTranslationFilter
에게 예외를 던져 처리한다. (try-catch)Filter 내부에서 try-catch 구문을 통해 예외 발생 시, HandlerExceptionResolver를 빈으로 주입받아 @ExceptionHandler에서 처리하는 방법
@ExceptionHandler애노테이션을 통해 Controller의 메서드에서 throw된 Exception에 대한 공통적인 처리를 할 수 있다.
@RestController
public class TestController {
private final Logger logger = LoggerFactory.getLogger(UserController.class);
// 예외 핸들러
@ExceptionHandler(value = TestException.class)
public String controllerExceptionHandler(Exception e) {
logger.error(e.getMessage());
return "/error/404";
}
@GetMapping("hello1")
public String hello1() {
throw new TestException("hello1 에러 "); // 강제로 예외 발생
}
@GetMapping("hello2")
public String hello2() {
throw new TestException("hello2 에러 "); // 강제로 예외 발생
}
}
TestController내에서 발생하는 TestException에 대해서 예외가 발생하면 controllerExceptionHandler
메서드에서 모두 처리해준다.
@ExceptionHandler
가 예외를 처리하게 된다.@ControllerAdvice
@RestControllerAdvice
@ControllerAdvice는 DispatcherServlet에서 발생하는 예외만 처리할 수 있다.
@RestControllerAdvice
public class GlobalExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = TestException.class)
public String testExceptionHandler(Exception e) {
logger.error(e.getMessage());
return "/error/404";
}
}
Controller의 @ExceptionHandler와 ControllerAdvice의 @ExceptionHandler중 높은 우선순위는?
@GetMapping("/api/v1/members")
public ResponseEntity getAllMember(Pageable pageable) {
Page<Member> memberPage;
// 반복적으로 발생하는 Try-Catch
try {
memberPage = memberFindService.getAllMemberPage(pageable);
} catch (RuntimeException re) {
return ResponseEntity.badRequest().build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return ResponseEntity.ok().body(MemberAdminDto.Info.of(memberPage));
}
이런 식으로 할 수 있지만, 각 컨트롤러마다 try-catch를 써주면 양이 엄청나게 늘어날 것이다. 또한 성공 로직과 실패로직이 뒤섞여 있어 가독성이 매우 떨어진다.
try/catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다. - 클린코드 -
https://velog.io/@aidenshin/Spring-Boot-Exception-Controller