Java/SpringBoot

[SpringBoot] Exception Handler 예외를 통합관리 하자.!!!

JoJun's 2022. 4. 15. 10:53
728x90
반응형

요즘 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참고.!)

728x90
반응형
댓글수0