프로그래밍에서 예외 처리는 아주 중요하면서도 아주 어렵다. 하지만 좋은 애플리케이션을 만드는데 중요한 부분을 차지하기 때문에 상세하고 다양하게 예외를 잡아 처리해준다면 클라이언트와 서버에서 더 안정적인 프로그램이 될 수 있다.
Java에서는 예외 처리를 위해 try-catch를 사용해야 하지만 이를 모든 코드에 붙이는 것은 비효율적이다. Spring은 에러 처리라는 공통 관심사(cross-cutting concerns)를 메인 로직으로부터 분리하는 다양한 예외 처리 방식을 고안하였고, 예외 처리 전략을 추상화한 HandlerExceptionResolver
인터페이스를 만들었다.
적합한 예외 처리를 위해 HandlerExceptionResolver 구현체들을 빈으로 등록해서 관리한다.
우선순위대로 아래의 4가지 구현체들이 빈으로 등록되어 있고 이들 중 적용 가능한 구현체를 찾아 예외 처리를 한다.
DefaultErrorAttributes : 에러 속성을 저장하며 직접 예외를 처리하지는 않는다. )ExceptionHandlerExceptionResolver : API 예외를 처리한다.ResponseStatusExceptionResolver : 예외에 따라 HTTP 상태 코드를 지정해주는 역할을 한다.DefaultHandlerExceptionResolver : 스프링 내부의 기본 예외들을 처리한다.DefaultErrorAttributes 는 직접 예외를 처리하지 않고 속성만 관리하므로 성격이 다르다.
그래서 내부적으로는 이것을 제외하고 직접 예외를 처리하는 나머지 3가지 ExceptionResolver들을 HandlerExceptionResolverComposite 이라는 파일에 모아서 관리한다.

Spring은 아래와 같은 도구들로 ExceptionResolver를 동작시켜 에러를 처리할 수 있다.
Spring에서는 API 예외 처리와 같은 문제를 해결하기 위해 @ExceptionHandler 라는 어노테이션을 사용하여 매우 편하게 처리할 수 있도록 해준다.
해당 어노테이션으로 예외가 처리되는 곳이 ExceptionHandlerExceptionResolver 이다.
@ExceptionHandler 는 Controller 계층에서 예외를 잡아서 메서드로 처리해주는 기능이다.
@ExceptionHandler 어노테이션을 선언하고, 해당 컨트롤러에서 처리하고 싶은 Exception 클래스들을 속성으로 받아 처리할 예외로 지정할 수 있다.
@RestControllerAdvice
public class MyController {
@ExceptionHandler(handleNoSuchElementFoundException.class)
public ResponseEntity<String> handleException(handleNoSuchElementFoundException ex) {
// 예외 처리 로직
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal Server Error");
}
}
해당 controller에서 예외가 발생하면 ExceptionHandlerExceptionResolver 가 먼저 controller에 @ExceptionHandler 가 있는 지 찾고, 있으면 호출해준다. 이렇게 오류해결을 시도하고, 정상흐름으로 변경된다.
지정한 예외 또는 그 예외의 자식 클래스는 모두 잡을 수 있고
@ExceptionHandler({ Exception1.class, Exception2.class}) 와 같은 형태로 여러 예외를 한번에 지정할 수 있다. 만약 예외 클래스를 지정하지 않는다면, 파라미터에 설정된 에러 클래스를 처리하게 된다.
@ExceptionHandler 사용 시에 주의할 점은 @ExceptionHandler 에 등록된 예외 클래스와 파라미터로 받는 예외 클래스가 동일해야 한다는 것이다. 만약 값이 다르다면 스프링은 컴파일 시점에 에러를 내지 않다가 런타임 시점에 에러를 발생시킨다.
@ExceptionHandler 는 컨트롤러에 구현하므로 특정 컨트롤러에서만 발생하는 예외만 처리된다. 하지만 컨트롤러에 에러 처리 코드가 섞이며, 에러 처리 코드가 중복될 가능성이 높다.
그래서 스프링은 전역적으로 예외를 처리할 수 있는 좋은 기술을 제공해준다.
Spring은 전역적으로 여러 컨트롤러에 대해 @ExceptionHandler 를 적용할 수 있는 @ControllerAdvice 와 @RestControllerAdvice 어노테이션을 제공하고 있다. 두 개의 차이는 @Controller 와 @RestController의 차이와 같이 @ResponseBody가 붙어 있어 응답을 Json으로 내려준다는 점에서 다르다.
@ControllerAdvice 어노테이션을 사용하여 클래스를 선언하면, 해당 클래스가 전역 예외 처리를 위한 Advice 클래스로 사용된다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(handleNoSuchElementFoundException.class)
public ResponseEntity<String> handleException(handleNoSuchElementFoundException ex) {
// 예외 처리 로직
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal Server Error");
}
}
@ControllerAdvice 어노테이션에는 @Component 어노테이션이 포함되어 있어서 ControllerAdvice가 선언된 클래스는 스프링 빈으로 등록된다. 위와 같이 전역적으로 에러를 핸들링하는 클래스를 만들어 어노테이션을 붙여주면 에러 처리를 위임할 수 있다.
@ControllerAdvice 는 전역적으로 적용되지만, 만약 특정 클래스에만 제한적으로 적용하고 싶다면 @RestControllerAdvice 의 basePackages 등을 설정함으로써 제한할 수 있다.
@RestControllerAdvice(basePackageClasses = MyController.class)
public class CustomRestControllerAdvice {
// 예외 처리 메서드
}
장점
주의사항