일친 (IlChin) - AOP를 통한 공통 에러 처리

no.oneho·2025년 5월 29일
0

일친 개발기

목록 보기
5/17

이번 포스팅은 비즈니스 로직을 시작하기 전 마지막 단계로
AOP를 통해 공통 에러를 처리해보는 시간을 가지겠다.

먼저 AOP가 무엇인가 부터 간단하게 설명하자면 Aspect Oriented Programming, 우리나라 말로는 관점지향 프로그래밍 정도로 불린다

그래서 이게 뭔데? 관점적으로 지향하는 프로그래밍을 어디다 쓰는건데?

이게 무슨말이냐하면 프로젝트를 개발하는 개발자의 관점을 생각해보자
이때 핵심적으로 생각해야할 관점이 존재할 것이고, 부가적으로 생각할 관점이 존재할것이다.

예를들어 비즈니스 로직, 즉 프로젝트를 개발하는 목적과 연관되며 고민을 오래하고 시간을 쏟아야하는것을 핵심적인 관점으로 바라보도록 한다면?

부가적인 관점은 메서드 시간체크, 로그스탬프, 에러처리 등은 개발자가 비즈니스 로직을 개발할 때 부가적으로 생각해야할 것들이 된다.

그렇다면 관점지향 프로그래밍을 적용하게 되면 어떻게 변할까?

쉽게말해 부가적으로 따라오는것들을 비즈니스 코드와 함께 코딩을 해야하는게 아니라 알아서 처리되도록 프로그래밍을 해두는것이 관점지향 프로그래밍을 쉽게 설명한것이 된다.

살짝 딥하게 들어가자면 스프링 빈의 생명주기에 따라 컨텍스트 내부에 외부 요청이 들어오게되면 AOP를 사용하지 않았을 때 비즈니스 로직과 함께 돌게되어 코드의 복잡성과 개발자의 피로도가 높아지지만

AOP를 사용하면 외부 요청이 들어올때 중간에 요청을 가로채서 처리할 부분을 처리해 준 후 요청이 도착하여 비즈니스 로직을 수행할 수 있다.

그럼 AOP를 사용하여 에러처리를 해보도록 하자

먼저 에러에 들어갈 내용이 담긴 인터페이스를 하나 생성해준다

Error.class

public interface Error {
    HttpStatus getStatus();
    String getMessage();
}

그리고 이 인터페이스를 상속받아주는 클래스를 하나 더 생성한다

CustomCxception.class

@Getter
public class CustomException extends RuntimeException{

    private final Error error;
    private final String errorMessage;

    public CustomException(Error error) {
        super(error.getMessage());
        this.error = error;
        this.errorMessage = error.getMessage();
    }
    
}

마지막으로 비즈니스나 도메인, 뭐 한곳에 몰아도 상관없지만 에러를 미리 정의해둘 ENUM 파일을 생성해준다.

SampleError.class

@Getter
@AllArgsConstructor
public enum SampleError implements Error {


    SAMPLE_ERROR(HttpStatus.BAD_REQUEST, "sample error")
    ;

    private final HttpStatus status;
    private final String message;

}

이제 준비는 끝났고, 서두에서 설명한 AOP를 사용하러 가보자

GlobalExceptionHandler.class

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Response<Void>> handleException(Exception e) {
        Response<Void> errorResponse = Api.error(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(CustomException.class)
    public ResponseEntity<Response<Void>> handleCustomException(CustomException e) {
        Response<Void> errorResponse = Api.error(e.getError().getStatus(), e.getMessage());
        return new ResponseEntity<>(errorResponse, e.getError().getStatus());
    }
}

@RestControllerAdvice 는 Controller단에 AOP를 적용하기위한 어노테이션이다.
나머지는 본인에 코드에 맞게 에러를 처리해주면 된다.

특이사항으로는 Exception 전체를 받는 handleException 메서드가 있는데 예외처리를 실수로 못했을 경우 500처리를 해주는 메서드라고 보면 된다.

이제 이걸 실 코드에 적용을 하면

   @GetMapping("error")
    public Response<String> error() {
        int a = 1;
        if (a == 1) {
            throw new CustomException(SampleError.SAMPLE_ERROR);
        }
        return Api.success(200, "성공 메시지", "Hello World");
    }

이렇게 간단하게 적용을 할 수 있다.

실제로 비즈니스 코드에 적용 할 때는 try/catch가 확연하게 줄어 가독성도 좋아 항상 사용하고 있다.

다만 딱 한가지 문제가 있는데 해당 기능만으로는 필터단에서 발생하는 Exception을 잡을수가 없다

예를들어 회원의 인증/인가 부분의 경우 Spring Security를 사용하게되면 대부분 Filter를 사용하여 앞단을 처리하게 될텐데 그렇게되면 요청이 들어올 때

요청 ---> 필터 ---> AOP(에러처리) ---> 컨트롤러

이런 형식으로 도달하게 된다. 그러므로 필터단에서 에러가 발생해도 내가 원하는 형태로 리스폰스를 잡아줄수가 없는데 요건 다음 포스팅에 회원 인증/인가 부분을 개발하며 함께 다뤄보겠다

profile
이렇게 짜면 요구사항이나 기획이 변경됐을 때 불편하지 않을까? 라는 생각부터 시작해 설계를 해나가는 개발자

0개의 댓글