본문 바로가기

[Kotlin&Spring] 5기 내일배움캠프

[Kotlin&Spring] 5기 아웃소싱 프로젝트 트러블슈팅 - AOP 에러 해결

 

주문 요청이 발생하면 aop 를 구현하여 로그를 찍는 것이 요구사항이었다
기존 필수 구현 사항이었던 주문 기능을 구현하고 로깅 기능도 구현하였으며 테스트도 완료했다
도전 기능인 장바구니 기능을 추가하게 되면서 장바구니 기능과 주문기능을 연결했다
그로 인해 주문에 대한 end point 가 변경되었다
또한 주문이 생성될 때 매개변수로 받는 값에 대한 변경도 있었다
OrderCreateRequest -> @PathVariable 이용한 Id, OrderCreateRequest
기존에는 OrderCreateRequest 만 받았기 때문에 아래의 코드로도 동작했다

 

    // 주문 요청 로그
    @Around("execution(* com.example.foodduck.order..*createOrder(..))")
    public Object logOrderCreate(ProceedingJoinPoint joinPoint) throws Throwable {
        OrderCreateRequest request = (OrderCreateRequest) joinPoint.getArgs()[0];
        Long userId = request.getUserId();
        Long menuId = request.getMenuId();
        
        //...중략
        
        return result;
    }

 

하지만 장바구니 id 값이 추가가되어 그 후에는 500 서버 에러가 발견되었다
500 서버 에러가 발생한 이유는 GlobalExceptionHandler의 최상위 예외인 Exception 에 대한 에러로 잡혔기 때문이었다

@ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) {
        Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
        logger.error("서버 오류 발생: ", ex); //로그에 예외 메시지 출력
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("서버 오류 발생: " + ex.getMessage());
    }
}

 

에러메세지는 다음과 같이 로그되었다

2025-03-07T05:24:42.177+09:00 ERROR 20956 --- [foodduck] [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: jakarta.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'jakarta.validation.constraints.NotBlank' validating type 'java.lang.Long'. Check configuration for 'storeId'] with root cause

jakarta.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'jakarta.validation.constraints.NotBlank' validating type 'java.lang.Long'. Check configuration for 'storeId'
   at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getExceptionForNullValidator(ConstraintTree.java:116) ~[hibernate-validator-8.0.2.Final.jar:8.0.2.Final]
//... 이하 생략

 

UnexpectedTypeException 과 ClassCastException 이 발생했는데, 이는 AOP 에서  joinPoint.getArgs()[0] 값이 실질적으로 Long 타입 id가 들어왔는데 OrderCreateRequest 타입으로 캐스팅 하려고 했는데 실패했기 때문이었다
@Valid 를 매개변수에 적용하고 OrderCreateRequest 의 필드에 다음과 같은 검증 어노테이션을 적용하였기 때문에 null 값이 들어오면 에러가 항상 발생한다고 판단했다
따라서 AOP 메서드 내부에서 NPE 가 발생하지 않을 것이라고 판단하여 다음과 같이 메서드를 수정했다
instance of 타입 연산자를 사용해 Long 일 경우 shoppingCartId 값으로 할당하고 OrderCreateRequest일 경우 request 로 할당하도록 했다
그 후 에러가 발생하지 않고 원할하게 메서드를 실행할 수 있었다

// 주문 요청 로그
    @Around("execution(* com.example.foodduck.order..*createOrder(..))")
    public Object logOrderCreate(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        Long userId = null;
        OrderCreateRequest request;
        for (Object arg : args) {
            if (arg instanceof Long) {
                userId = (Long) arg;
            } else if (arg instanceof OrderCreateRequest) {
                request = (OrderCreateRequest) arg;
            }
        }
        // ... 중략
        return result;
    }

 

회고 결과

 

에러 메세지를 읽고 코드의 흐름을 따라가면서 프로젝트의 전체적인 구조를 파악할 수 있었다 콘솔 프린트, 로깅, IDE 디버그 등 디버깅을 다양하게 시도해보는 경험이 되었다