세 줄 요약
- 전역 예외 핸들러를 통해 예외 처리 코드에 대한 유지 보수성 및 기존 코드에 대한 가독성을 높일 수 있다.
- @ControllerAdvice 및 @ExeptionHandler 어노테이션을 통해 동일한 예외를 전역적으로 처리할 수 있다.
- 사용자 예외를 정의할 땐 RuntimeException을 상속받아 Unchecked Exception으로 만든다.
전역 예외 핸들러
지난 포스팅에서 500 Internal Server Error를 400 Bad Request로 변환해야 한다고 했습니다.
전역 예외 핸들러를 이용하면 응답 변환과 더불어 예외 발생시 클라이언트에게 유용한 정보를 함께 제공할 수 있습니다.
전역 예외 핸들러
예외 처리에 관한 내용을 모아둔 클래스를 의미합니다.
전역 예외 핸들러를 사용하면 기존 코드의 가독성이 향상되고 예외 처리 코드의 유지 보수가 수월해지는 이점이 있습니다.
@ContorllerAdvice
예외를 '전역적으로(Globally)' 처리하기 위한 어노테이션입니다.
여러 개의 컨트롤러에서 발생한 동일한 예외에 대해 한 곳(전역 예외 핸들러 클래스)에서 처리할 수 있도록 해줍니다.
@RestControllerAdvice 어노테이션은 뷰를 렌더링 하는 대신 message conversion을 통해 JSON을 반환합니다.
→ @ControllerAdvice + @ResponseBody
해당 어노테이션에 관한 자세한 내용은 여기에서 확인할 수 있습니다.
@ExceptionHandler
value로 원하는 예외(.class)를 설정해 던져줄 수 있습니다.
또한 value로 설정한 예외에 대한 처리를 정의해서 클라이언트에게 유용한 정보를 제공할 수 있습니다.
해당 어노테이션이 선언된 메소드에서 응답 변환 및 정보 제공이 이루어집니다.
사용예시
어플리케이션 계층인 ProductService에서 발생한 ConstraintViolationException 예외 처리를 위한 메소드를 정의합니다.
해당 메소드를 통해 500 Interner Server Error를 400 Bad Request로 변환하고 클라이언트에게 유용한 정보를 제공합니다.
GlobalExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
// 처리하고자 하는 예외(.class)를 value로 설정
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException ex) {
List<String> errors = ex.getConstraintViolations().stream()
.map(cv -> extractField(cv.getPropertyPath()) + " :: 해당 필드는 " + cv.getMessage())
.toList();
// 응답에는 400 Bad Request를 담고, 전달할 메세지에는 유효한 정보들(errors)을 담아 리턴한다.
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
// 경로(path)에서 필드를 가져오기 위한 메소드
public String extractField(Path path) {
String[] arr = path.toString().split("\\.");
int lastIndex = arr.length - 1;
return arr[lastIndex];
}
}
Postman 전송 결과(처리 전)
Postman 전송 결과(처리 후)
예외 처리 전략
예외의 종류와 상속 관계
자바에서 예외(Exception)라 함은 Exception 클래스와 이 클래스를 상속받는 모든 하위 클래스를 말합니다.
Exception 클래스는 Throwable 클래스를 상속받기 때문에 모든 예외는 throw 키워드에 의해 던져질 수 있습니다.
예외는 어플리케이션에서 자연스럽게 발생하는 현상입니다.
반면 에러(Error)는 'OutOfMemoryError' 혹은 'StackOverFlow'와 같이 메모리와 관련해 발생한 문제를 말합니다.
이러한 문제들은 원래라면 발생하지 않아야 하고, 발생한 에러는 로직으로 처리하는데 어려움이 있습니다.
Checked Exception
Exception 클래스를 상속받는 예외와 해당 예외를 다시 상속받는 예외를 의미합니다.
try-catch 문을 강제하는 것이 특징이며, 대표적인 Checked Exception에는 IOException이 있습니다.
PrintStream 클래스의 write() 메소드는 IOException을 발생시킬 수 있기 때문에 try-catch로 예외처리를 해야 합니다.
Unchecked Exception
RuntimeException을 상속받는 예외와 해당 예외를 다시 상속받는 예외를 의미합니다.
try-catch 문을 강제하지 않는 것이 특징이며 대표적인 Unchecked Exception에는 NullPointException이 있습니다.
예외 처리 전략
다음과 같은 이유로 인해 Unchecked Exception으로 사용자 예외를 정의합니다.
실제로 예외 발생 시 try-catch 문으로 처리할 수 없는 경우가 대부분이다.
Unchecked Exception에서도 필요하다면 try-catch 문을 통해 처리할 수 있다.
람다 표현식에서 깔끔하게 처리가 가능하다.
사용자 예외 정의
List 타입의 레포지토리에서 상품 조회시 조회된 값이 없으면 'NoSuchElementException' 예외를 던집니다.
만약 레포지토리가 DB로 바뀐다면 조회된 값이 없는 상황에서 위와는 다른 예외를 던질 것입니다.
이처럼 외부에서 레포지토리가 무엇으로 구성된 지 알게 된다면 캡슐화를 깨뜨리는 예외가 됩니다.
같은 상황에 대해 각각 다른 예외가 발생하는 일이 없도록 사용자 예외를 정의해서 처리합니다.
조회된 결과가 없다는 의미로 'EntityNotFoundException'을 정의해서 처리해보겠습니다.
EntityNotFoundException
public class EntityNotFoundException extends RuntimeException {
public EntityNotFoundException(String message) {
super(message);
}
}
Unchecked Exception으로 만들기 위해 RuntimeException을 상속받고, super() 메소드를 통해 초기화를 진행합니다.
GlobalExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
(생략)
// 사용자 예외 정의
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<ErrorMessage> handleEntityNotFoundException(EntityNotFoundException ex) {
List<String> errors = new ArrayList<>();
errors.add(ex.getMessage());
return new ResponseEntity(errors, HttpStatus.NOT_FOUND);
}
}
ProductListRepository
@Repository
public class ListProductRepository {
(생략)
public Product findById(Long id) {
return products.stream()
.filter(product -> product.sameId(id))
.findFirst()
// .orElseThrow() ---> 기존엔 NoSuchElementException 발생
.orElseThrow(() -> new EntityNotFoundException("Product를 찾지 못했습니다"));
}
}
Postman 전송 결과(처리 전)
Postman 전송 결과(처리 후)
* refs
https://www.tcpschool.com/java/java_exception_class
https://tecoble.techcourse.co.kr/post/2023-05-03-ExceptionHandler-ControllerAdvice/
https://velog.io/@banjjoknim/RestControllerAdvice
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-advice.html
'Spring-Java > Spring' 카테고리의 다른 글
DB를 사용하는 Repository 구성 2 (0) | 2024.05.08 |
---|---|
DB를 사용하는 Repository 구성 1 (0) | 2024.05.03 |
@Valid, @Validated (0) | 2024.04.25 |
@RequiredArgsConstructor (1) | 2023.12.21 |
@RequestBody, @ResponseBody (feat.@ModelAttribute) (0) | 2023.12.18 |