요즘 YOPLE 서비스를 개발하면서 Exception을 핸들링해야 하는 일이 생겼다.
스프링 구조상 컨트롤러-서비스-JDBC lib를 통해 API가 처리되면서 Controller단에서 Throw 되는 Exception을 핸들링하고자 한다.
기존 예외 처리 방식은 trty-catch를 각 메소드별로 설정해주어 처리했다. 각 Exception별로 처리하는 중복 코드가 try-catch별로 생성되어 코드가 방대해지고 복잡해졌다.....
그래서 찾아보니 @ControllerAdvice와 @ExceptionHandler 두 어노테이션으로 Exception을 전역으로 처리할 수 있다고 한다.!!!
@ExceptionHandler
SpringFramwork Docs에서 @ExceptionHandler는 아래와 같이 설명하고 있다.
"Annotation for handling exceptions in specific handler classes and/or handler methods."
특정 클래스의 메서드에서 Exception핸들링을 하기 위한 어노테이션이다. @Controller, @RestContorller가 아닌 @Service, @Repository의 Bean에서는 사용할 수 없다.
실제 사용은 아래와 같다.
@Controller
public class ExceptionController {
// ...
@ExceptionHandler(value = RuntimeException.class)
public ResponseEntity<Object> handle(RuntimeException ex) {
// ...
}
}
ExceptionController에서 발생하는 RuntimeException을 handle() 메서드로 공통적으로 핸들링이 가능하다.!!
주의할 점은. 해당 메서드는 ExceptionController에서 발생하는 RuntimeException만 핸들링이 가능하다는 것이다.
그렇다면 이 메서드를 모든 컨트롤러에 작성을 해주어야 하는데...... 이러한 문제를 @ControllerAdvice로 해결이 가능하다.
@ControllerAdvice
ControllerAdvice (Spring Framework 5.3.19 API)
Specialization of @Component for classes that declare @ExceptionHandler, @InitBinder, or @ModelAttribute methods to be shared across multiple @Controller classes. Classes annotated with @ControllerAdvice can be declared explicitly as Spring beans or auto-d
docs.spring.io
@ControllerAdvice는 모든 컨트롤러(@Controller, @RestController)에 @ControllerAdvice에 정의된 메서드들을 매핑시켜준다.
@ControllerAdvice
public class GlobalExceptionHandler {
private final Logger logger = LogManager.getLogger(GlobalExceptionHandler.class);
// 사용자 정의 예외
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ResponseJsonObject> handleRuntimeException(RuntimeException ex) {
......
return new ResponseEntity<>(ResponseJsonObject.withStatusCode(ApiStatusCode.SYSTEM_ERROR), HttpStatus.BAD_REQUEST);
}
// 로그인시 존재하지 않는 유저인 경우 발생하는 Exception
@ExceptionHandler(UsernameNotFoundException.class)
public ResponseEntity<ResponseJsonObject> handleUsernameNotFoundException(UsernameNotFoundException ex) {
......
return new ResponseEntity<>(ResponseJsonObject.withStatusCode(ApiStatusCode.USER_NOT_FOUND), HttpStatus.OK);
}
// @RequestBody, @RequestHeader 유효성 실패.
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ResponseJsonObject> handleConstraintViolationException(ConstraintViolationException ex) {
......
return new ResponseEntity<>(response, HttpStatus.OK);
}
// @RequestBody 유효성 실패.
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status, WebRequest request) {
.....
return new ResponseEntity<>(response, HttpStatus.OK);
}
// 사용자 정의 예외
@ExceptionHandler(YOPLEServiceException.class)
public ResponseEntity<ResponseJsonObject> handleYOPLEServiceException(YOPLEServiceException ex) {
......
return new ResponseEntity<>(ex.getResponseJsonObject(), HttpStatus.OK);
}
}
GlobalExceptionHandler.class에 정의된 모든 메서드는 모든 Controller에 매핑되고, 각 메소드는 각 컨트롤러에서 발생하는 Exception을 핸들링하는 @ExceptionHandler로 예외를 관리한다.
** 만약 AOP를 사용하여 컨트롤러 메서드의 JoinPoint를 잡아 후 처리를 해준다면. JoinPoint에서 Exception이 발생하면 AOP에서도 Exception을 Throw 해주어야 Handler에서 캐치할 수 있다. (spring life cycle참고.!)
'Java > SpringBoot' 카테고리의 다른 글
[Spring Boot] Filter (0) | 2022.06.08 |
---|---|
[SpringBoot] JPA Entity Listener 엔티티 이벤트 리스너 (2) | 2022.04.26 |
[SpringBoot] Log4j2 기본 사용법 (0) | 2022.03.07 |
[SpringBoot] SOP, CORS 이야기 (0) | 2022.02.03 |
[SpringBoot] Security 인증 절차 시 DB Access 여러번 일어나는 이슈. (0) | 2022.01.12 |