GlobalException

단어 그대로 전역으로 Exception을 관리한다는 의미를 포함하고 있다. 전역으로 Exception을 관리하기 위해 @ExceptionHandler를  사용법을 정리했습니다. 


 

GlobalException 처리를 하는 이유

케이스별로 여러 이유가 있겠지만 나는 아래와 같은 이유로 많이 사용한다.

 

클라이언트에게 정확한 버그 원인을 보낼 수 있다.

예상가능한 범위의 Exception처리를 하면 해당 Exception에 대해 클라이언트에게 원하는 StatusCode, Message 를 가공해 보낼 수 있어 버그의 원인을 빠르게 파악할 수 있다.

 

유지보수가 좋다.

StatusCode 및 Message를 특정 클래스에서 관리하기 때문에 유지보수가 편하며,  특정 Exception을 관리할 수 있다.

 

그 외

메모리 사용량 감소하며, 로딩 시간이 줄어든다. 라는 말도 있지만 사실 하드웨어가 좋아짐에 따라 상이할 수도 있고 아직 체감하지는 못했다.


하지만

불필요한 성능 저하를 막기 위해  아무 trace도 갖지 않도록 직접 fillInStackTrace()를 오버라이딩 처리할 수 있으며, 
static final 키워드를 이용해 예외를 미리 캐싱(로드)해둘 수 있다고 해서 충분히 가능할 것 같다는 생각도 듭니다.

 

 

주의할 점

  1. @ExceptionHandler는 Controller 및 RestController 또는 @ControllerAdvice, @RestControllerAdvice에만 적용이 가능하다.

  2. @Controller 또는 @RestController 클래스 내에 @ExceptionHandler를 사용한다면 해당 클래스내에 Exception만 관리 할 수 있습니다. 

  3. Error와 Exception의 차이는 분명이 존재한다. 즉, 전역으로 Exception 처리한다면 RuntimeException, IllegalArgumentException,  Exception을 예외로 처리하는 건 불적절 할 수도 있기에 CustomException을 만들어 사용하는게 여러 측면에서 편리합니다.

 

 


 

@ExceptionHandler 사용법  

 

@RestController 및 @Controller에서 적용방법

아래 예제와 같이 주석으로 1번이라고 작성된 곳과 같이 예외처리 할 곳에 작성해주면 됩니다.  그러면@ExceptionHandler로 설정해준 메소드에 접근하는걸 확인이 가능합니다.  다만 @ExceptionHandler에  같은 Exception으로 설정해줘야합니다.  예를 들면, 아래 코드에서 1번의 Exception과 2번의 Exception이 같아야합니다. 

@RestController
 class TestController {

    @GetMapping("/hello")
    public void hello(){
        throw new RuntimeException("예외처리 메세지를 입력해주세요");  // 1번 
    }

    @ExceptionHandler(RuntimeException.class)  // 2번
    public Object error(RuntimeException e) {
        return e.getMessage();
    }
}

 

@RestController 와 @Controller로 설정된 클래스에서 사용할 경우 해당 클래스에서 예외처리가 발생했을 경우만 @ExceptionHandler에 접근이 가능합니다. 추가로 해당 클래스 메소드에서 Service에 있는 메소드를 호출했다면 그 메소드에서 예외처리를 해도  @ExceptionHandler가 실행됩니다. 단지 해당 클래스가 아닌 @ExceptionHandler가 없는 곳에서 Service의 메소드를 호출하면  @ExceptionHandler가 발생하지 않습니다.

 

 

 

@RestControllerAdvice및 @ControllerAdvice에서 적용 방법

위에 예제와 같지만 Advice가 붙는 어노테이션은 전역으로  @ExceptionHandler가 작용합니다. 즉 @ExceptionHandler가 없어도 모든 Controller에서 예외가 발생한다면 @ControllerAdvice  or  @RestControllerAdvice가 붙은 클래스의  @ExceptionHandler에 접근합니다.

@ControllerAdvice  or  @RestControllerAdvice
public class GlobalException {

    @ExceptionHandler(RuntimeException.class)
    public Object exception(RuntimeException e){
        return e.getMessage();
    }

}

 

@ControllerAdvice@RestControllerAdvice 차이?

출처 :  https://velog.io/@kiiiyeon/%EC%8A%A4%ED%94%84%EB%A7%81-ExceptionHandler%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC

@ControllerAdvice 

@ControllerAdvice는 @Controller와 handler에서 발생하는 에러들을 모두 잡아준다. 
@ControllerAdvice안에서 @ExceptionHandler를 사용하여 에러를 잡을 수 있다.

 

@RestControllerAdvice

@RestControllerAdvice는 @ControllerAdvice와 @ResponseBody을 가지고 있다. 
@Controller처럼 작동하며 @ResponseBody를 통해 객체를 리턴할 수 있다.

 

범위 설정

모든 에러를 잡아주기 때문에 일부 에러만 처리하고 싶을 경우에는 따로 설정을 해주면 된다.

  1. 어노테이션
  2. basePackages (+basepackagesClasses)
  3. assignableTypes
// 1.
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// 2.
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// 3.
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
  • basePackages: 탐색 패키지 지정, org.example.controllers 패키지, 하위 패키지까지 모두 탐색
  • basePackagesClasses: 탐색 클래스 지정, 클래스의 맨 위에 있는 package부터 시작

 

 

 

Exception Method

 

getStackTrace()

@ExceptionHandler(RuntimeException.class)
public Object error(RuntimeException e) {
    return e.getStackTrace();
}

응답 : 어떤 클래스에 어디 라인에 예외가 발생했는지 확인할 수 있습니다.

[
    {
        "classLoaderName""app",
        "moduleName"null,
        "moduleVersion"null,
        "methodName""hello",
       // 요기부터
        "fileName""TestController.java",
        "lineNumber"41,
        "className""com.example.demo.controller.TestController",
        // 요기까지
        "nativeMethod"false
    },
    {
        "classLoaderName"null,
        "moduleName""java.base",
        "moduleVersion""11.0.13",
        "methodName""invoke0",
        "fileName""NativeMethodAccessorImpl.java",
        "lineNumber"-2,
        "className""jdk.internal.reflect.NativeMethodAccessorImpl",
        "nativeMethod"true
    },
    {
        "classLoaderName"null,
        "moduleName""java.base",
        "moduleVersion""11.0.13",
        "methodName""invoke",
        "fileName""NativeMethodAccessorImpl.java",
        "lineNumber"62,
        "className""jdk.internal.reflect.NativeMethodAccessorImpl",
        "nativeMethod"false
    },
    { .....},
    { .....},
    .....
   
]
 
 
 
 
 

getMessage()

해석 그래로 자신이 메세지를 입력한 문자열데이터를 볼 수 있습니다.

@ExceptionHandler(RuntimeException.class)
public Object error(RuntimeException e) {
    return e.getMessage();
}

 

 

 

fillInStackTrace()

getStackTrace() 와 getMessage()를 전부 응답합니다.

응답

{
    "cause"null,
    "stackTrace": [
        {
            "classLoaderName""app",
            "moduleName"null,
            "moduleVersion"null,
            "methodName""error",
            "fileName""TestController.java",
            "lineNumber"41,
            "className""com.example.demo.controller.TestController",
            "nativeMethod"false
        },
        { .....},
        { .....},
         .....
    ],
    "message""예외처리 메세지를 입력해주세요",
    "suppressed": [],
    "localizedMessage""예외처리 메세지를 입력해주세요"
}

 

 

1부 끝

1부에는 이러한 글로벌하게 예외를 관리할 수 있는 방법을 정리했다면 2부는 CustomException과 ErroCode에 메세지와 statucCode를 관리 할 수 있는 예제를 정리 되어있습니다.

2022.09.01 - [분류 전체보기] - [스프링] GlobalException 처리 방법 및 예제 정리[2] - CustomException 이용하기

 

[스프링] GlobalException 처리 방법 및 예제 정리[2] - CustomException 이용하기

이전 글 2022.08.30 - [JAVA/스프링(Spring)] - [스프링] GlobalException 처리 방법 및 예제 정리[1](@ControllerAdvice, @ExceptionHandler, @RestControllerAdvice) [스프링] GlobalException 처리 방법 및 예..

whitewise95.tistory.com

 

복사했습니다!