이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Spring AOP는 Filter나 Interceptor와 달리 어떤 호출 경계를 감싸는가?
  • 트랜잭션, 캐싱, 감사 로그, 성능 측정은 왜 AOP가 잘 맞는가?
  • 공통 처리 요구사항을 받았을 때 Filter, Interceptor, AOP 중 하나를 고르는 질문 목록은 무엇인가?

개요

AOP는 HTTP 요청 체인에 붙는 도구가 아니다. Spring Bean 메서드 실행에 붙는 도구다. Spring AOP의 join point는 Spring Bean의 메서드 실행으로 제한되므로 필드 접근, 생성자 호출, new로 직접 만든 객체에는 적용되지 않는다.

그래서 AOP는 웹 요청뿐 아니라 배치, 메시지 리스너, 스케줄러, 다른 서비스 호출에서도 동일한 메서드 정책을 적용할 수 있다. 반대로 원시 HTTP 요청을 다뤄야 하는 문제라면 실행 위치가 이미 늦다.

AOP의 강점은 메서드 경계다. 트랜잭션, 캐싱, 재시도, 감사 로그, 성능 측정처럼 “이 Bean 메서드가 호출될 때 항상 적용되어야 하는 정책”은 AOP가 잘 맞는다.

AOP가 맞는 문제

Service 메서드 실행 시간을 측정하고 싶다면 AOP가 자연스럽다. Filter는 전체 HTTP 요청 시간을 측정하고, Interceptor는 Controller 전후 시간을 측정한다. 하지만 핵심 비즈니스 메서드 자체의 시간을 알고 싶다면 Service Pointcut이 더 정확하다.

@Aspect
@Component
public class ServiceTimingAspect {
 
    @Around("within(com.example..service..*)")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.nanoTime();
        try {
            return joinPoint.proceed();
        } finally {
            log.info("{} elapsed={}ns",
                    joinPoint.getSignature().toShortString(),
                    System.nanoTime() - start);
        }
    }
}

트랜잭션도 같은 원리다. @Transactional은 Spring AOP 기반 선언적 트랜잭션 모델로, Service 메서드 호출 전후에 트랜잭션 시작과 종료를 붙인다.

메서드 보안도 비슷한 경계에서 생각할 수 있다. 특정 Service 메서드 호출 자체를 권한으로 보호해야 한다면 method security가 자연스럽다. 그러나 URL, 세션, CSRF, 인증 흐름처럼 HTTP 보안 경계에 속한 문제는 Security FilterChain이 먼저다.

AOP가 안 맞는 문제

HTTP header를 조작하거나 raw request body를 캐싱하거나 CORS preflight를 처리해야 한다면 AOP는 위치가 늦다. Controller나 Service 메서드까지 오기 전에 처리되어야 하는 문제이기 때문이다.

Controller가 선택되었는지, 어떤 HandlerMethod 애너테이션이 있는지 보는 문제는 Interceptor가 더 직접적일 수 있다. 물론 Controller 메서드 AOP도 가능하지만 MVC lifecycle과 깊게 연결된 요구라면 MVC 확장점을 먼저 보는 편이 좋다.

프록시 한계

Spring AOP는 기본적으로 프록시 기반이다. 호출이 프록시 객체를 거쳐 Spring Bean에 도달할 때 강하다. 같은 클래스 내부 호출, 직접 new로 만든 객체, final 클래스와 메서드, private 메서드는 기대와 다르게 동작할 수 있다.

이 한계는 AOP 문서, 트랜잭션 문서, Method Validation 문서에서 반복해서 나온다. 애너테이션만 보지 말고 호출 경로를 보아야 한다.

선택 질문

첫째, 처리하려는 정보가 원시 HTTP request/response인가. 그렇다면 Filter를 먼저 본다.

둘째, HandlerMethod나 MVC model/view 흐름이 필요한가. 그렇다면 Interceptor나 MVC Advice를 본다.

셋째, HTTP와 무관한 Bean 메서드 정책인가. 그렇다면 AOP를 본다.

넷째, 응답 body 자체를 바꾸는가. 그렇다면 ResponseBodyAdvice나 message converter를 본다.

다섯째, 인증/인가인가. 그렇다면 Spring Security FilterChain을 먼저 본다.

실전 팁

  • AOP Pointcut은 넓게 시작하지 말고 의미 있는 패키지, 애너테이션, Bean 이름으로 제한한다.
  • Filter, Interceptor, AOP가 같은 로그를 중복 남기지 않게 레이어별 목적을 나눈다.
  • AOP가 동작하지 않으면 self-invocation과 Bean 등록 여부를 먼저 확인한다.
  • 보안과 트랜잭션처럼 프레임워크가 이미 AOP/Filter를 제공하는 영역은 표준 인프라를 우선 사용한다.
  • 공통 처리 도구 선택은 코드 줄 수보다 디버깅 가능성과 경계 명확성이 중요하다.

위험 신호!

  • 모든 공통 처리를 CommonAspect 하나에 넣는다.
  • HTTP 요청 조작을 Service AOP에서 처리한다.
  • AOP self-invocation 문제를 우회하려고 구조 없이 자기 자신을 주입한다.
  • Filter, Interceptor, AOP에서 같은 request id를 각각 생성한다.
  • 인증/인가를 직접 만든 AOP로 대체한다.

확인 질문

확인 질문

  • AOP가 가장 잘 맞는 공통 처리 경계는 무엇인가?
    • Spring Bean 메서드 호출 경계다.
  • AOP가 HTTP 원시 요청 처리에 약한 이유는 무엇인가?
    • Filter보다 늦게, Bean 메서드 호출 시점에 적용되므로 요청 앞단의 Servlet 정보를 직접 감싸기 어렵기 때문이다.
  • 공통 처리 도구를 선택할 때 가장 먼저 물어야 할 질문은 무엇인가?
    • 이 처리가 필요한 실행 위치와 필요한 컨텍스트가 무엇인지 물어야 한다.

참고 문서