Spring

(Spring Boot) AOP(Aspect Oriented Programming)를 이용한 예외처리

신동편 2023. 7. 20. 12:50
728x90

AOP

 

AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 쉽게 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화 하는 것이라고 이해할 수 있다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다. 

 

Exception같은 전체 로직 어디에서든지 동작할 수 있는 기능을 하나로 묶어 전역으로 처리하는 것으로 쉽게 이해할 수 있겠다.


@ExceptionHandler

 

@ExceptionHandler 어노테이션을 메서드에 선언하고 특정 예외 클래스를 지정해 주면해당 예외가 발생했을 때 메서드에 정의한 로직을 처리를 할 수 있다.

 

즉, 처리하고 싶은 Exception을 정의한 다음 해당 예외가 발생했을 때 처리하고 싶은 방법으로 처리할 수 있다는 것이다. 

 

@ExceptionHandler는 @Controller 가 적용된 Bean에서 발생하는 예외를 잡아 하나의 메서드에서 처리하는 기능을 수행한다.

 

@Controller
public class ExampleController {

    @ExceptionHandler(value = RuntimeException.class)
    public ResponseEntity<String> exceptionHanble(IOException ex) {
        // ...
    }
    
    public ResponseEntity<String> notExceptionHanble(IOException ex) {
        // ...
    }
}

즉, 위와 같은 상황에서 exceptionHanble 메서드에서 발생한 RuntimeException에 대해서는 @ExceptionHandler가 동작하지만, notExceptionHanble에서는 RuntimeException이 발생해도 @ExceptionHandler가 동작하지 않는다.

따라서 컨트롤러마다 중복된 @ExceptionHandler를 정의해 주어야 모든 에러에 대한 커버가 가능해진다.

 


@ControllerAdvice

 

위와 같은 이유로 @ControllerAdvice를 사용한다. 

@ControllerAdivice는 @Controller 어노테이션이 적용된 모든 곳에서의 발생되는 예외에 대해 Catch 한다.

 

@ControllerAdvice가 적용된 클래스에 정의되어 있는 @ExceptionHandler는 모든 컨트롤러에서 발생하는 예외에 대해서 동작하게 된다.

 

@ControllerAdvice
public class ExampleExceptionHandler {

    @ExceptionHandler(value = CustomException.class)
    public ResponseEntity<String> customExceptionHanble(CustomException ex) {
        // ...
    }
    
    @ExceptionHandler(value = BindException.class)
    public ResponseEntity<String> BindExceptionHanble(BindException ex) {
        // ...
    }
   
}

이렇게 정의하면 모든 Controller에서 발생하는 CustomException과 BindException에 대해서 해당 메서드가 실행되는 것이다.

 

또한, 이용해서 원하는 컨트롤러나 패키지만 선택할 수도 있다.

 

@RestControllerAdvice(basePackageClasses = MembersPostController.class)

@RestControllerAdvice(basePackages = "com.mogakko.be_final.domain")

이렇게 하위 로직에서 예외가 발생하게 되면, 상위로 타고 올라가 Controller까지 왔을 때 @ControllerAdvice이 에러를 잡아서 @ExceptionHandler에 선언된 Exception 인지 체크한다.

 


@RestControllerAdvice

 

@RestControllerAdvie는 @ControllerAdvice + @ResponseBody라고 할 수 있다.

 

 

따라서 @RestControllerAdvie로 선언하면 따로 @ResponseBody를 붙여주지 않아도 객체를 리턴할 수 있다.

에러 메시지를 DTO에 담아 리턴해 주는 방식으로 사용할 수 있다.

 


활용

 

@Getter
@AllArgsConstructor
public class CustomException extends RuntimeException {
    private final ErrorCode errorCode;
}

최근에 진행했던 프로젝트에서 활용했었던 CustomException을 예로 들어 설명하겠다.

 

@Getter
@AllArgsConstructor
public enum ErrorCode {

    PLZ_INPUT_REASON_OF_REPORT(HttpStatus.BAD_REQUEST, "신고 이유를 입력해주세요.");

    private final HttpStatus httpStatus;
    private final String data;
}

 

 

throw new CustomException(INVALID_EMAIL);

이런 식으로 예외가 발생했을 때,

 

@Getter
@Builder
public class ErrorResponse {
    private String message;
    private String data;

    // 에러 반환 형식
    public static ResponseEntity<ErrorResponse> toResponseEntity(ErrorCode errorCode) {
        return ResponseEntity
                .status(errorCode.getHttpStatus())
                .body(ErrorResponse.builder()
                        .message(errorCode.getData())
                        .build()
                );
    }
}

 정의한 DTO클래스에 따라서,

 

@RestControllerAdvice
public class GlobalExceptionHandler {
	
    @ExceptionHandler(value = {CustomException.class})
    public ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
        return ErrorResponse.toResponseEntity(e.getErrorCode());
    }
    
}

@Controller 빈에서 던져지는 에러에 대해 전역적으로 처리해 줄 @ExceptionHandler를 @ControllerAdivce 혹은 @RestControllerAdivce가 정의된 클래스에 있는  CustomException Hanbler에 의해서 처리되는 것이다.

 

{
"message": "신고 이유를 선택해주세요.",
"data": null
}

정의한 DTO 클래스의 형식에 따라 리턴되는 것을 볼 수 있다. 

 

 

728x90

'Spring' 카테고리의 다른 글

@SpringBootTest / @WebMvcTest  (0) 2023.07.25
Spring Rest Client  (0) 2023.07.25
스프링 컨테이너 (Spring Container)  (0) 2023.07.24
의존성 주입(Dependency Injection, DI)  (0) 2023.07.23