본문 바로가기

TIL

AOP

 🔖 핵심 주제

Interceptor / AOP의 구조이해와 활용.

💡 배운 내용

1. 요청 처리 흐름 이해 (Filter → Interceptor → Controller → AOP)

  • Filter는 인증(JWT 파싱)
  • Interceptor는 인가(admin 권한 체크)
  • AOP는 메서드 실행 전후 로깅, 트랜잭션, 예외 처리 등에 활용 될 수 있다

추가적으로 Interceptor 에서 발생한 예외가 Filter의 catch에서 잡히는 이유는 chain.doFilter() 내부에서 발생했기 때문이라는 점을 이해했다.

예를들어 jwt Filter 에서 이런 에러 처리가 있을때, Interceptor 에서 에러가 발생하면 해당 에러가 반환된다.

} catch (Exception e) {
            log.error("예상치 못한 오류: URI={}", url, e);
            sendErrorResponse(httpResponse, HttpStatus.INTERNAL_SERVER_ERROR, "요청 처리 중 오류가 발생했습니다.");
        }

즉, doFilter 내부에서 interceptor가 동작한다는 것을 알 수 있다.

 

2. Admin API 실행 전후로 요청/응답을 JSON으로 로깅하는 AOP를 구현했다.

- @Aspect, @Pointcut, @Around 등의 에노테이션이 무엇인지 확인하고 사용 해보았다.

- Interceptor 와 다르게 webConfig에 등록하지 않고 메서드를 직접 지정 하는 방식.

- AOP 내부에서 요청정보를 가져오는 법을 학습하고 사용해보았다

 

-전체 소스 코드

@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class LoggingAspect {
    private final ObjectMapper objectMapper;

    @Pointcut("execution(* org.example.expert.domain.user.service.UserAdminService.*(..)) || " +
            "execution(* org.example.expert.domain.comment.controller.CommentAdminController.*(..))")
    private void adminApi() {}

    @Around("adminApi()")
    public Object logAdminApi(ProceedingJoinPoint joinPoint) throws Throwable {
        UUID requestId = UUID.randomUUID();
        // 요청 시간 확인
        String requestTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

        // attributes 가져오기
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        //attributes 가 없으면 에러 발생
        if(attributes == null){
            log.error("HTTP 요청 컨텍스트를 찾을 수 없습니다. (RequestAttributes is null)");
            throw new IllegalStateException("해당 AOP는 HTTP 요청 내부에서만 실행 가능합니다.");
        }

        // 요청 정보 가져오기
        HttpServletRequest request = attributes.getRequest();

        //url 정보
        String url = request.getRequestURI();

        // attribute 에 저장된 userId가져오기
        Long userId = (Long) request.getAttribute("userId");

        // body args list
        Object[] args = joinPoint.getArgs();

        log.info(">>> 요청 ID= {} | 요청 데이터  userId= {} 시간= {} 주소= {} Body= {}",
                requestId ,userId, requestTime, url, objectMapper.writeValueAsString(args));

        Object result;
        //서비스 실행
        result = joinPoint.proceed();

        // 결과 response 반환
        String responseBody = (result != null) ? result.toString() : "No Response";
        log.info("<<< 요청 ID= {} | API Response:[Body: {}]",requestId, responseBody);

        return result;
    }
}

 

 

🚀 문제 해결 및 트러블슈팅
- 문제 상황 : enum 검증을 위해 ValidEnum 작성중 적용 되지 않던 현상

1. 기존엔 enum 검증을 서비스 단에서 수행 -> dto단에서 검증하고 서비스에서는 바로 enum 사용을 하고자 함

-> ValidEnum 이라는 개념을 적용 시켜보려 시도 중 타입을 찾지 못하는 에러 발생.

 

 

-시도한 방법 : ConstraintValidator<ValidEnum, UserRole>으로 선언 하여 해결 시도 -> 

UserRole 타입으로 선언된 순간 Jackson이 역직렬화할 때 이미 enum 값 검증을 해버리기 때문에, @ValidEnum까지 올 일이 없다, 

유효하지 않은 값이 오면 그 전에 터집니다. -> 의미 없는 행위


- 해결 방안 

단순 DTO에서 타입설정 및 NotNull -> 이후 globalException 에서 HttpMessageNotReadableException 를 관리

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class SignupRequest {

    @NotBlank @Email
    private String email;
    @NotBlank
    private String password;
    @NotNull
    private UserRole userRole;
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<Map<String, Object>> enumValidation(final HttpMessageNotReadableException e) {
		HttpStatus status = HttpStatus.BAD_REQUEST;
		String message = e.getMessage();
		return getErrorResponse(status, message);
}



오늘 잘한 점

  • 단순히 “왜 에러 나지?”에서 끝나지 않고 실행 흐름을 구조적으로 분석했다.
  • Filter와 Interceptor의 역할을 명확히 분리했다.
  • AOP를 실제 요구사항에 맞게 구현해봤다.

새로 알게 된 점

  • Interceptor 예외가 Filter catch에 잡히는 건 정상 흐름이라는 것
  • AOP는 단순 로깅 도구가 아니라 “관심사의 분리”를 구현하는 핵심 기술이라는 것
  • ValidEnum의 기본 사용법과 사용 이유.

내일 더 학습하고 싶은 내용

  • 테스트 커버리지 고려한 테스트 코드작성
  • 과제 리팩토링

'TIL' 카테고리의 다른 글

유닛테스트와 통합테스트  (0) 2026.03.05
테스트코드  (0) 2026.03.04
github - 협업준비하기.  (0) 2026.02.15
SOLID 원칙  (1) 2026.02.06
ORM 과 JPA  (1) 2026.02.03