예외 처리
1. 예외 처리
프로그래밍에서 예외(exception)란 입력 값의 처리가 불가능하거나 참조된 값이 잘못된 경우 등 애플리케이션이 정상적으로 동작하지 못하는 상황이다. 예외는 개발자가 직접 처리할수 있으므로 미리 코드 설계를 통해 처리할 수 있다.
에러(error)는 주로 자바의 가상머신에서 발생시키는 것으로 예외와 달리 애플리케이션 코드에서 처리할 수 있는 것이 거의 없다. 예를 들어 메모리 부족(OutOfMemory), 스택 오버플로(StackOverFlow)가 있다.
(1) 예외 클래스
자바의 예외 클래스는 아래와 같은 상속 구조를 갖추고 있다.
Exception 클래스는 Checked Exception 과 Unchecked Exception 으로 구분할 수 있다.
Checked Exception (컴파일 단계에서 확인 가능한 예외 상황) |
Unchecked Exception (문법상 문제는 없지만 프로그램이 동작하는 도중 예기치 않은 상황이 생겨 발생하는 예외) |
|
처리 여부 | 반드시 예외 처리 필요 | 명시적 처리를 강제하지 않음 |
확인 시점 | 컴파일 단계 | 실행 중 단계 |
대표적인 예외 클래스 | IOException SQLException |
RuntimeException NullPointerException IllegalArgumentException IndexOutOfBoundException SystemException |
(2) 예외 처리 방법
예외가 발생했을 때 이를 처리하는 방법은 예외 복구, 예외 처리 회피, 예외 전환 3가지가 있다.
1. 예외 복구
예외 상황을 파악해서 문제를 해결하는 방식이다.
대표적인 예시가 try/catch 구문이다.
2. 예외 처리 회피
예외가 발생한 시점에서 바로 처리하는 것이 아니라 예외가 발생한 메서드를 호출한 곳에서 에러 처리를 할 수 있게 전가하는 방식이다. 이때 throw 키워드를 사용해 어떤 예외가 발생했는지 호출부에 내용을 전달할 수 있다.
3. 예외 전환
예외가 발생했을 때 어떤 예외가 발생했느냐에 따라 호출부로 예외 내용을 전달하면서 좀 더 적합한 예외 타입으로 전달할 필요가 있다. 또는 애플리케이션에서 예외 처리를 좀더 단순하게 하기 위해 래핑(wrapping)해야 하는 경우도 있다. 이런 경우 try/catch 방식을 사용하면서 catch 블록에서 throw 키워드를 사용해 다른 예외 타입으로 전달하면 된다.
(3) 스프링 부트의 예외 처리 방식
예외가 발생했을 때 클라이언트에 오류 메시지를 전달하려면 각 레이어에서 발생한 예외를 엔트포인트 레벨인 컨트롤러로 전달해야 한다. 이렇게 전달받은 예외를 스프링 부트에서 처리하는 방식으로 크게 2가지가 있다.
1. @(Rest)ControllerAdvice와 @ExceptionHandler를 통해 모든 컨트롤러의 예외를 처리
@RestControllerAdvice
public class CustomExceptionHandler {
private final Logger LOGGER = LoggerFactory.getLogger(CustomExceptionHandler.class);
@ExceptionHandler(value = RuntimeException.class)
public ResponseEntity<Map<String, String>> handlerException(
RuntimeException e, HttpServletRequest request
) {
HttpHeaders responseHeaders = new HttpHeaders();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
LOGGER.error("Advice 내 handlerException 호출, {}, {}",
request.getRequestURI(), e.getMessage());
...
// (생략)
}
}
@RestcontrollerAdvice 와 @ControllerAdvice는 스프링에서 제공하는 어노테이션이다. @Controller 나 @RestController에서 발생하는 예외를 한 곳에서 관리하고 처리할 수 있게 하는 기능을 수행한다.
@ExceptionHandler는 @Controller나 @RestController 가 적용된 빈에서 발생하는 예외를 잡아 처리하는 메서드를 정의할 때 사용한다. value 속성으로 어떤 예외 클래스를 처리할지 등록한다.
2. @ExceptionHandler를 통해 특정 컨트롤러의 예외를 처리
@(Rest)ControllerAdvice 는 별도 범위 설정이 없으면 전역 범위에서 예외를 처리한다.
따라서 특정 컨트롤러에서만 동작하는 @ExceptionHandler 메서드를 생성해서 처리할 수도 있다.
컨트롤러 클래스 내부에 handleException() 을 생성한다. 즉, 해당 클래스에 국한해서 예외 처리를 할 수 있다.
@RestController
@RequestMapping("/exception")
public class ExceptionController {
private final Logger LOGGER = LoggerFactory.getLogger(ExceptionController.class);
@ExceptionHandler(value = RuntimeException.class)
public ResponseEntity<Map<String,String>> handleException(
RuntimeException e, HttpServletRequest request
) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpstatus = HttpStatus.BAD_REQUEST;
...
// (생략)
}
}
만약, @ControllerAdvice와 컨트롤러 내에 동일한 예외 타입을 처리한다면 좀 더 우선순위가 높은 클래스 내의 핸들러 메서드가 사용되는 것을 볼 수 있다.
(3) 커스텀 예외
커스텀 에외는 만드는 목적에 따라 생성하는 방법이 다르다.
커스텀 예외 클래스를 생성하는데 필요한 내용은 3가지 이다.
- 에러 타입 (error type) : HttpStatus 의 reasonPhrase
- 에러 코드 (error code) : HttpStatus 의 value
- 메시지 (message) : 상황별 상세 메시지