본문 바로가기
IT 정보

Spring boot에서 ControllerAdvice를 이용하여 Exception handling하기

by 완기 2023. 3. 21.
728x90
반응형

 

코드를 작성하다 보면 코드에서 Exception(이하 오류)이 발생하는 일은 빈번하다.

제일 무서운 NullPointerException부터 NumberFormatException , ClassCastException 등등 많은 종류의 오류가 있다.

 

 

이런 오류들 중,

Checked Exception으로 컴파일 이전에

코드 작성시에 try catch나 throw 등 에러를 핸들링하도록 강제하는 코드들이 있는 반면

 

SQL Exception, NullPointer, OutOfMemory 등의 오류들은 런타임에서 발생하기 때문에

개발 단계에서 확인하지 못하고 운영 단계나 사용자의 실 사용시에 발생하기 때문에 이는 곧 소프트웨어의 품질 문제로 이어진다.

 

MVC 패턴을 채택하여 개발한다면 가장 첫 진입점이 되는 컨트롤러에 try catch절을 추가하거나 해도 되지만, 

요청에 따른 적절한 메서드를 호출하는 컨트롤러에 책임에 벗어나는 행위이므로 좋지 않다.

비즈니스 로직을 처리하는 Service 클래스로 넘기자.

 

그렇다면 여기서 문제가 생긴다.

 

어디서 날지 모르는 Exception을 모든 서비스 코드에 Try catch를 달아야 하나?

이는 생산성도 떨어지고 추후에 Exception별로 다른 비즈니스 로직이 실행되어야 할 때, 굉장히 골치 아플 수 있다.

 

그런데 여기서 한 번 생각해 볼 만한 점이 있다.

Exception이 주로 비슷한 패턴으로 발생한다는 것이다.

 

 

이를테면 컨트롤러에서는 SQLException이 날 리 없다.

컨트롤러에서 쿼리를 날리지 않는 이상.

 

주로 Repository나 DAO라고 불리는 데이터 액세스 계층에서 발생할 것이고,

NullPointerException은 주로 비즈니스 로직을 처리하는 서비스 계층에서 Null인 객체를 참조하려다가 날 것이다.

 

그리고 이에 더해, 모든 Repository 처리 전, 후로 Try Catch를 대신해 주는 객체가 있으면 좋겠다는 생각도 할 수 있다.

바로 이 점이 Proxy라는 의미인데, 대신해 주는, 전달자 등의 의미를 가지고 있다.

 

프록시 패턴,공통 로직 처리 즉 AOP를 떠올릴 수 있다.

 

AOP는 실제로 설정에 따라 프록시 객체를 중간에 끼워 넣어 공통 로직을 처리한다.

 


사용방법

Exception을 공통적으로 처리할 클래스 파일을 하나 만들고

@RestControllerAdvice

어노테이션을 달아주면 된다.

 

 

@RestControllerAdvice
public class ExceptionHandlerAdvice {

    @ExceptionHandler(value = { IllegalAccessException.class }) // Exception을 처리할 클래스를 배열로 나열
    protected ResponseDto illegalAccessExceptionHandler(Exception e, Object body, WebRequest request) {
        return new ResponseDto(HttpStatus.BAD_REQUEST, e);
    }

    @ExceptionHandler(value = { NoHandlerFoundException.class })
    protected ResponseDto noHandlerFoundException(Exception e, Object body, WebRequest request, HttpServletRequest servletRequest) {
        String uri = servletRequest.getRequestURI();
        String method = servletRequest.getMethod();
        return new ResponseDto(HttpStatus.BAD_REQUEST, method + " " + uri + " is not found !");
    }
}

내가 작성한 예시 파일은 위 코드와 같다.

 

내 비즈니스 로직에선 인증/인가를 받지 않은 유저는 IllegalAccessException을 던지도록 했고,

이 익셉션은 컨트롤러, 서비스 등의 별도의 핸들링을 하지 않아도 모두 illegalAccessExceptionHandler 메서드를 실행한다.

 

마찬가지로 NoHandlerFoundException은 내가 정의하지 않은 API에 대해 접근할 경우,

스프링이 요청에 대해 적절한 핸들러를 찾지 못해 발생시키는 Exception이다.

application.yml파일에 핸들러를 찾지 못하면 에러를 발생시키도록 설정해 두고

 

해당 에러가 발생하면 공통 로직이 마찬가지로 처리된다.

 

 

 

728x90
728x90

댓글