<목차>
1. @ExceptionHandler
2. @RestControllerAdvice / @ControllerAdvice 사용하여 처리하기
3. 컨트롤러에서 처리하기
REST API 를 제공하는 애플리케이션 입장에서는, 요청값을 처리하던 중 예외가 발생하면 예외를 복구하여 정상적으로 처리하기 보다는, 요청을 보낸 클라이언트에게 어떤 문제가 발생하였는지를 전달해주는 경우가 많다.
따라서, 각 레이어에서 예외가 발생했을 경우, 발생한 예외를 엔드포인트 레벨인 컨트롤러로 전달해야 하며, 컨트롤러에서 클라이언트로 오류 메시지를 전달하는 방법에 대해 알아보자.
Exception 에 대한 내용은, 아래 포스팅을 참고.
[JAVA] 예외처리 - Exception 이해하기
1. Error & Exception2. Exception Class 구조3. Exception 처리 방법 [Error & Exception]Error / 에러JAVA 에서 Error 는 JVM에서 발생시키는 것으로, 애플리케이션 코드에서 처리할 수 있는 부분이 거의 없다.대표적으
jh4dev.tistory.com
[@ExceptionHandler]
스프링 프레임워크에서 제공하는 어노테이션으로, 특정 예외가 발생했을 때 그 예외를 처리하는 메서드를 지정하는 데 사용한다.
주로 컨트롤러나 @ControllerAdvice 클래스 내에서 사용되며, 발생한 예외를 잡아 적절한 응답을 반환할 수 있도록 해준다.
- 특정 예외 처리: @ExceptionHandler는 특정 예외 클래스를 인자로 받아, 해당 예외가 발생했을 때 실행될 메서드를 정의하며, 여러 예외를 한 메서드에서 처리할 수도 있다.
- 컨트롤러 범위: 이 어노테이션 해당 컨트롤러 클래스에서 발생하는 예외에만 적용된다. 하지만, @ControllerAdvice와 함께 사용하면 애플리케이션 전체의 예외를 전역적으로 처리할 수 있다.
- 맞춤형 응답: @ExceptionHandler를 사용하여 발생한 예외에 대해 사용자 정의 응답을 생성할 수 있다. 예를 들어, JSON 형식의 에러 메시지를 반환하거나 특정 HTTP 상태 코드를 설정할 수 있다.
- 예외의 계층 구조 처리: 예외의 계층 구조에 따라 더 구체적인 예외부터 처리되며, 만약 특정 예외에 대한 핸들러가 없다면, 부모 클래스의 예외 핸들러가 호출될 수 있다.
- 간편한 예외 관리: @ExceptionHandler를 사용하면 예외 처리 로직을 분리하여 컨트롤러 코드의 가독성을 높이고, 다양한 예외 상황에 맞춰 일관된 에러 응답을 제공할 수 있다.
[@ControllerAdvice/ @RestControllerAdvice 사용하여 처리하기]
REST API 를 제공하는 서비스라는 전제 하에 @RestControllerAdvice 기준으로 작성하였다.
<CustomExceptionHandler> @RestControllerAdvice 어노테이션
//@RestcontrollerAdvice(basePackages = "특정 패키지 지정")
@RestControllerAdvice
@Slf4j
public class CustomExceptionHandler {
@ExceptionHandler(value = RuntimeException.class)
public ResponseEntity<Map<String, String>> handleException(RuntimeException re, HttpServletRequest request){
HttpHeaders responseHeaders = new HttpHeaders();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
log.error("Advice 내 handleException() 호출, {}, {}", request.getRequestURI(), re.getMessage());
Map<String, String> responseMap = new HashMap<>();
responseMap.put("error_type", httpStatus.getReasonPhrase());
responseMap.put("code", "400");
responseMap.put("message", re.getMessage());
return ResponseEntity.status(httpStatus).body(responseMap);
}
}
- @RestControllerAdvice : 결과 값을 JSON 형태로 반환한다. @Controller 어노테이션이 적용된 컨트롤러도 처리할 수 는 있으나, View 기반 응답을 반환하는 @Controller 에는 부적합할 수 있다.
- @ExceptionHandler : @Controller 또는 @RestController 가 적용된 빈에서 발생하는 예외를 Catch 하여 처리하는 메서드를 정의할 때 사용한다.
value 속성을 통해, 어떤 Exception 클래스를 처리할 지 지정할 수 있으며, 배열 형태로도 지정할 수 있다.
<ExceptionController> 테스트 컨트롤러 생성
@RestController("/exception")
@Slf4j
public class ExceptionController {
@GetMapping("/basic")
public String basicExceptionController() throws RuntimeException {
throw new RuntimeException("기본 Exception Handler 테스트");
}
}
- 위에서 작성한 CustomExceptionHandler 에서 RuntimeException 을 처리하게 하였으므로, 강제로 RuntimeException 을 발생시키는 컨트롤러를 구성하여 테스트 진행.
테스트 결과, 아래와 같은 JSON 형태의 Response 가 반환된 것을 확인할 수 있다.
{
"code": "400",
"error_type": "Bad Request",
"message": "기본 Exception Handler 테스트"
}
[컨트롤러에서 처리하기]
필요로 하는 컨트롤러 내에서, @ExceptionHandler 어노테이션으로 처리할 예외를 작성해주면 된다.
위에서 작성한 내용을 사용하자면,
CustomExceptionHandler의, handleException() 을 컨트롤러로 옮겨주면 된다.
<ExceptionController>
@RestController("/exception")
@Slf4j
public class ExceptionController {
@ExceptionHandler(value = RuntimeException.class)
public ResponseEntity<Map<String, String>> handleException(RuntimeException re, HttpServletRequest request){
HttpHeaders responseHeaders = new HttpHeaders();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
log.error("Controller 내 handleException() 호출, {}, {}", request.getRequestURI(), re.getMessage());
Map<String, String> responseMap = new HashMap<>();
responseMap.put("error_type", httpStatus.getReasonPhrase());
responseMap.put("code", "400");
responseMap.put("message", re.getMessage());
return ResponseEntity.status(httpStatus).body(responseMap);
}
@GetMapping("/basic")
public String basicExceptionController() throws RuntimeException {
throw new RuntimeException("기본 Exception Handler 테스트");
}
}
다만, 이미 @RestControllerAdvice 를 통해 작성된 Handler 가 있다면 다음과 같은 규칙으로 우선순위가 지정되어 핸들러가 호출된다.
- 예외 타입의 레벨에 따른 우선순위
- @ExceptionHandler(Exception.class) 와 @ExceptionHandler(RuntimeException.class) 두 핸들러가 존재한다면, 보다 구체적인 @ExceptionHandler(RuntimeException.class) 가 우선순위를 갖는다.
- 핸들러 위치에 따른 예외 처리 우선 순위
- 지금과 같이, @RestControllerAdvice 와 같은 글로벌 예외처리와, @Controller 내 의 컨트롤러 예외처리가 존재한다면, 범위가 좁은 컨트롤러의 핸들러가 우선순위를 갖는다.
테스트를 진행하여 우선순위를 확인해보자.
CustomExceptionHandler 에 작성된 핸들러 메서드와 ExceptionController 에 작성된 핸들러 메서드는 로그 적재 부분을 다르게 하였다.
<console>
[ERROR] [http-nio-8080-exec-6] com.springboot.valid.controller.ExceptionController Controller 내 handleException() 호출, /basic, 기본 Exception Handler 테스트
위와 같이 Controller 내 작성된 핸들러 메서드가 호출된 것을 확인할 수 있다.
'Spring & Spring boot' 카테고리의 다른 글
[Spring Boot] 예외 처리 - 3. CustomException 사용하기 (0) | 2024.08.16 |
---|---|
[Spring Boot] 예외 처리 - 2. 유효성 검사 Exception 처리 (0) | 2024.08.16 |
[Spring Boot] Validation - 4. 커스텀 Validation 설정하기 (0) | 2024.08.15 |
[Spring Boot] Validation - 2. @Validated 어노테이션 사용방법 (0) | 2024.08.14 |
[Spring Boot] Validation - 1. @Valid 어노테이션 사용방법 (0) | 2024.08.14 |