에러를 처리할 때 관심사를 분리해서 더 효율적으로 처리할 수 있는데, 그러려면 먼저 AOP에 대해서 알아야 한다.
Spring AOP는 스프링 프레임워크에서 제공하는 기능 중 하나로 관점 지향 프로그래밍을 지원하는 기술이다.
이렇게 말하면 무슨뜻인지 잘 이해가 안되겠지만, 위 사진의 예처럼 로깅, 보안, 트랜잭션 관리 등과 같은
공통적인 관심사를 모듈화하여 코드 중복을 줄이고 유지 보수성을 향상하는데 도움을 준다.
즉, 부가기능을 각 핵심기능1, 2, 3에 따로 작성하지 않고, 따로 모듈화하여 여러 객체에서 공통적으로 사용되는 코드이다.
여러 개의 클래스에서 반복해서 사용하는 코드가 있다면, 해당 코드를 모듈화하여 공통 관심사로 분리한다.
이렇게 분리한 공통 관심사를 Aspect로 정의하고 Aspect를 적용할 메서드나 클래스에 Advice를 적용하여,
공통 관심사와 핵심 관심사를 분리할 수 있다.
이렇게 AOP에서는 공통 관심사를 별도의 모듈로 분리하여 관리하며, 이를 통해 코드의 재사용성과 유지 보수성을 높일 수 있다.
주요 용어 이해
주요 어느테이션
Build.gradle내에 의존성을 추가한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
이렇게 AOP를 알아봤으니, 이제 API 예외처리를 알아보자.
웹 애플리케이션에서의 에러를 Client와 Server 모두가 잘 알지 못하면,
서비스하는 환경에서 발생하는 에러에 대해서 제대로 대응 할 수가 없다.
먼저 HTTP 에러 메세지 전달 방법을 이해해보면,
start-line(상태줄) : API 요청 결과 (상태 코드, 상태 텍스트)
HTTP/1.1 404 Not Found
HTTP 상태 코드의 종류
이제 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
);
}
}
참고)
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도 확인해보자.
개발자 도구에서 확인해보니, 값이 잘 전달된 것 같다.