2023.09.16 - AOP와 API 예외처리 (복습)

mjjin·2023년 9월 16일
0

Spring AOP와 API 예외처리

에러를 처리할 때 관심사를 분리해서 더 효율적으로 처리할 수 있는데, 그러려면 먼저 AOP에 대해서 알아야 한다.

AOP

Spring AOP는 스프링 프레임워크에서 제공하는 기능 중 하나로 관점 지향 프로그래밍을 지원하는 기술이다.

이렇게 말하면 무슨뜻인지 잘 이해가 안되겠지만, 위 사진의 예처럼 로깅, 보안, 트랜잭션 관리 등과 같은

공통적인 관심사를 모듈화하여 코드 중복을 줄이고 유지 보수성을 향상하는데 도움을 준다.

즉, 부가기능을 각 핵심기능1, 2, 3에 따로 작성하지 않고, 따로 모듈화하여 여러 객체에서 공통적으로 사용되는 코드이다.

여러 개의 클래스에서 반복해서 사용하는 코드가 있다면, 해당 코드를 모듈화하여 공통 관심사로 분리한다.

이렇게 분리한 공통 관심사를 Aspect로 정의하고 Aspect를 적용할 메서드나 클래스에 Advice를 적용하여,

공통 관심사와 핵심 관심사를 분리할 수 있다.

이렇게 AOP에서는 공통 관심사를 별도의 모듈로 분리하여 관리하며, 이를 통해 코드의 재사용성과 유지 보수성을 높일 수 있다.

주요 용어 이해

  • Aspect - 공통적인 기능들을 모듈화 한것을 의미한다.
  • Target - Aspect가 적용될 대상을 의미하며 메서드, 클래스 등이 이에 해당된다.
  • Join Point - Aspect가 적용될 수 있는 시점을 의미하며 메서드 실행 전, 후 등이 될 수 있다.
  • Advice - Aspect의 기능을 정의한 것으로 메서드의 실행 전, 후, 예외 처리 발생 시 실행되는 코드를 의미한다.
  • Point cut - Aspect를 적용할 메서드의 범위를 지정하는 것을 의미한다.

주요 어느테이션

  • @Aspect - 해당 클래스를 Aspect로 사용하겠다는 것을 명시한다.
  • @Before - 대상 "메서드"가 실행되기 전에 Advice를 실행한다.
  • @AfterReturning - 대상 "메서드"가 정상적으로 실행되고 반환된 후에 Advice를 실행한다.
  • @AfterThrowing - 대상 "메서드에서 예외가 발생"했을 때 Advice를 실행한다.
  • @After - 대상 "메서드"가 실행된 후에 Advice를 실행한다.
  • @Around - 대상 "메서드" 실행 전, 후 또는 예외 발생 시에 Advice를 실행한다.

Build.gradle내에 의존성을 추가한다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-aop'
}

이렇게 AOP를 알아봤으니, 이제 API 예외처리를 알아보자.

API 예외처리

웹 애플리케이션에서의 에러를 Client와 Server 모두가 잘 알지 못하면,

서비스하는 환경에서 발생하는 에러에 대해서 제대로 대응 할 수가 없다.

먼저 HTTP 에러 메세지 전달 방법을 이해해보면,

  • 1. 서버가 응답을 보낼 때 start-line에 있는 응답 코드를 보냈다.
  • 2. 응답 헤더에는 API 요청에 대한 상태코드를 함께 보낸다.
  • 3. 상태코드는 세자리로 되어있는 해당 번호에는 각각의 의미가 있다.
  • 4. 모든 상태코드를 알 순 없지만, 자주 쓰는 몇가지의 코드와 가장 앞자리숫자가 의미하는 바는 알아야 한다.

start-line(상태줄) : API 요청 결과 (상태 코드, 상태 텍스트)

HTTP/1.1 404 Not Found

HTTP 상태 코드의 종류

  • 2xx Success → 200번대의 상태코드는 성공을 의미한다.
  • 4xx Client Error → 400번대의 상태코드는 클라이언트 에러, 즉 잘못된 요청을 의미한다.
  • 5xx Server Error → 500번대의 상태코드는 서버 에러, 즉 정확한 요청에 서버쪽 사유로 에러가 난 상황을 의미한다.

이제 AOP를 이용하여 예외처리를 해보자.

먼저 예외처리 클래스를 하나 만들어야한다.

RestApiException Class

@Getter
@AllArgsConstructor
public class RestApiException {
    private String errorMessage;
    private int statusCode;
}

이렇게 클래스를 생성하여 @Getter 애노테이션을 사용하고, 에러메세지와 상태코드를 담아준다.

우리는 글로벌 예외처리 (AOP를 활용한 예외처리)를 해야한다.

@ControllerAdvice는 Spring에서 예외처리를 위한 클래스 레벨 어노테이션이다.

이 어노테이션은 모든 Controller에서 발생한 예외를 처리하기 위해 사용된다.

@ControllerAdvice가 붙은 클래스에서는 @ExceptionHandler메서드를 정의하여 예외를 처리하는 로직을 담을 수 있다.

그럼 @ControllerAdvice를 사용하는 이유는 무엇일까?

예외처리를 중앙 집중화하기 좋기 때문이다. 아까 AOP를 활용하여 예외처리를 한다 하였는데 바로 이 뜻이다.

각각의 Controller에서 예외처리 로직을 반복하지 않아도 됨으로 코드의 중복을 방지하고 유지보수성을 높일 수 있다.

또한, @ControllerAdvice를 사용하면 예외 처리 로직을 모듈화하여 관리하기 쉽기 때문에,

팀 내에서 공통된 예외 처리 로직을 공유하거나 다른 팀에서 예외 처리를 참고할 수 있다.

이를 통해 개발 생산성을 향상시키는 것도 가능하다.

다시 @ControllerAdvice를 적용하기 위해 클래스를 하나 생성해보자.

GlobalExceptionHandler Class

@RestControllerAdvice // @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {

    @ExceptionHandler({IllegalArgumentException.class})
    public ResponseEntity<RestApiException> handleException(IllegalArgumentException ex) {
        RestApiException restApiException = new RestApiException(ex.getMessage(), HttpStatus.BAD_REQUEST.value());
        return new ResponseEntity<>(
                // HTTP body
                restApiException,
                // HTTP status code
                HttpStatus.BAD_REQUEST
        );
    }
}
  • @ExceptionHandler는 Spring에서 예외처리를 위한 어노테이션이다.
  • 이 어노테이션은 특정 Controller에서 발생한 예외를 처리하기 위해 사용된다.
  • @ExceptionHandler가 붙어있는 메서드는 Controller에서 예외가 발생했을 때 호출되며,해당 예외를 처리하는 로직을 담고있다.
  • AOP를 이용한 예외처리 방식이기때문에, 메서드마다 try catch할 필요없이 깔끔한 예외처리가 가능하다.

참고)

IllegalArgument

java.lang.IllegalArgumentException은 적합하지 않거나(illegal) 적절하지 못한(inappropriate) 인자를 메서드에

넘겨주었을 때 발생한다.

이제 공부했던 프로젝트에 적용하여 예외처리를 사용해보자.

우리는 FolderService를 처리하는 FolderController 클래스를 가지고 있다. 위에 모든 컨트롤에서 예외처리를 시켰다.

FolderService Class

// import 생략

@Service
@RequiredArgsConstructor
public class FolderService {

    private final FolderRepository folderRepository;

    public void addFolders(List<String> folderNames, User user) {
        List<Folder> existFolderList = folderRepository.findAllByUserAndNameIn(user, folderNames);

        List<Folder> folderList = new ArrayList<>();

        for (String folderName : folderNames) {
            if(!isExistFolderName(folderName, existFolderList)) {
                Folder folder = new Folder(folderName, user);
                folderList.add(folder);
            } else {
                throw new IllegalArgumentException("중복된 폴더명을 제거해주세요! 폴더명 : " + folderName);
            }
        }

        folderRepository.saveAll(folderList);
    }

위에 GlobalExceptionHandler 클래스를 다시 봐보면

HTTP body에는 IllegalArgumentException에 담긴 메세지와 상태코드(restApiException)가 반환되고,

HTTP status code에는 상태코드(HttpStatus.BAD_REQUEST)가 반환된다.

그럼 이제 실행해보고 웹에서 확인해보자.

이렇게 1,2,3,4,5,6,7 이라는 폴더를 미리 만들어놓았다. 폴더 추가를 할 때, 이 이름이 있는 폴더를

생성하려하면 에러 메세지가 발생해야하는데, 한번 확인해보자.

이미 있는 폴더를 생성하려하면, 이렇게 우리가 설정한 IllegalArgumentExceptiond에 담긴 메세지가 호출된다.

그럼 HTTP body와 HTTP Status Code도 확인해보자.

출처 : https://storm-growth.tistory.com/49

0개의 댓글