Spring AOP 매커니즘 이해하기🚀

성종호·2024년 11월 6일
1

오늘은 Spring AOP의 내부 메커니즘을 깊이 있게 파헤쳐보고, HttpRequestLoggingAdvice 클래스를 예제로 설명하겠습니다. 또한, AOP의 최적화 방식과 @RestControllerAdvice가 왜 Spring의 AOP 방식과 다르게 동작하는지에 대해 알아보겠습니다 😊


1. Spring AOP의 기본 구성 요소 🛠️

AOP(Aspect-Oriented Programming)는 프로그램의 공통 관심사를 핵심 비즈니스 로직과 분리하여 모듈화하는 프로그래밍 패러다임입니다. 스프링 AOP는 프록시 패턴을 기반으로 메서드 실행 전후에 특정 부가 기능을 자동으로 적용할 수 있게 해줍니다.

Spring AOP는 몇 가지 핵심 구성 요소로 이루어져 있습니다. 이를 이해하면 HttpRequestLoggingAdvice가 어떻게 동작하는지 쉽게 파악할 수 있습니다.

1.1. Aspect

  • Aspect는 공통 관심사를 모듈화한 것으로, 하나 이상의 어드바이스와 포인트컷을 포함합니다.
  • @Aspect 어노테이션을 사용하여 정의합니다.

1.2. Advice

  • Advice는 실제로 공통 관심사 로직을 구현하는 부분입니다.
  • Before, After, Around, AfterReturning, AfterThrowing 등의 유형이 있습니다.
  • @Around는 메서드 실행 전후에 로직을 실행할 수 있도록 합니다.

1.3. Pointcut

  • Pointcut은 어떤 조인 포인트(메서드 실행 등)에 어드바이스를 적용할지를 정의하는 규칙입니다.
  • AspectJ 표현식을 사용하여 포인트컷을 정의합니다.

1.4. Join Point

  • Join Point는 어드바이스가 적용될 수 있는 지점으로, 주로 메서드 실행 시점을 의미합니다.

1.5. Advisor

  • Advisor는 포인트컷과 어드바이스를 결합한 개념입니다.
  • 특정 포인트컷에 매칭되는 어드바이스를 포함합니다.

2. JDK 동적 프록시와 CGLIB 프록시 비교 🔍

Spring AOP는 두 가지 주요 프록시 메커니즘을 사용합니다: JDK 동적 프록시CGLIB 프록시. 각각의 특성과 사용 시점을 이해하는 것은 매우 중요합니다.

2.1. JDK 동적 프록시

  • 인터페이스 기반: 프록시가 생성될 때 대상 클래스가 구현한 인터페이스를 기반으로 프록시를 생성합니다.
  • InvocationHandler 사용: 메서드 호출을 가로채기 위해 InvocationHandler를 사용합니다.
  • 장점:
    • 런타임 성능이 우수합니다.
    • 인터페이스를 기반으로 하므로, 프록시가 대상 클래스와 동일한 인터페이스를 구현합니다.
  • 단점:
    • 대상 클래스가 인터페이스를 구현하지 않으면 사용할 수 없습니다.

2.2. CGLIB 프록시

  • 클래스 기반: 대상 클래스의 서브클래스를 생성하여 프록시를 만듭니다.
  • MethodInterceptor 사용: 메서드 호출을 가로채기 위해 MethodInterceptor를 사용합니다.
  • 장점:
    • 대상 클래스가 인터페이스를 구현하지 않아도 프록시를 생성할 수 있습니다.
  • 단점:
    • 런타임에 클래스를 상속받아 서브클래스를 생성하기 때문에, 메모리 사용량이 증가할 수 있습니다.
    • 대상 클래스와 메서드가 final일 경우 프록시 생성이 불가능합니다.

2.3. Spring 설정에서의 프록시 선택

Spring AOP는 기본적으로 대상 빈이 하나 이상의 인터페이스를 구현하고 있으면 JDK 동적 프록시를 사용합니다. 그렇지 않거나, proxyTargetClass=true로 설정된 경우 CGLIB 프록시를 사용합니다.

@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
    proxyCreator.setProxyTargetClass(false); // 인터페이스 기반 프록시 사용 (JDK 동적 프록시)
    return proxyCreator;
}

3. HttpRequestLoggingAdvice 클래스 분석 📚

이제 HttpRequestLoggingAdvice 클래스를 살펴보겠습니다. 이 클래스는 Spring AOP를 활용해 HTTP 요청을 로깅하는 역할을 합니다.

@Component
@Aspect
public class HttpRequestLoggingAdvice {
    private static final Logger logger = LoggerFactory.getLogger(HttpRequestLoggingAdvice.class);

    @Around("@within(com.jongho.common.annotaition.HttpRequestLogging)")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().toShortString();
        Object[] args = joinPoint.getArgs();
        logger.info("Method: " + methodName + ", parameters: " + Arrays.toString(args));

        return joinPoint.proceed();
    }
}

주요 어노테이션 및 구성 요소

  • @Component: 이 클래스를 Spring의 빈으로 등록합니다.
  • @Aspect: 이 클래스가 AOP의 Aspect임을 나타냅니다.
  • @Around: 메서드 실행 전후에 특정 로직을 실행할 수 있는 어드바이스를 정의합니다.
  • @within: 특정 어노테이션이 적용된 클래스의 모든 메서드에 어드바이스를 적용합니다.
  • ProceedingJoinPoint: 실제 메서드를 호출하기 위해 proceed() 메서드를 제공합니다.

4. HttpRequestLoggingAdvice의 동작 과정 🔄

이제 HttpRequestLoggingAdvice 클래스가 Spring AOP에서 어떻게 동작하는지 단계별로 살펴보겠습니다.

4.1. 어플리케이션 컨텍스트 초기화 🌱

  1. 스프링 컨테이너 생성: AnnotationConfigApplicationContextAppConfig 및 기타 설정 클래스를 로드하여 빈을 정의하고 초기화합니다.
  2. 빈 스캔 및 등록: @Component 어노테이션이 있는 HttpRequestLoggingAdvice 클래스가 스캔되어 빈으로 등록됩니다.

4.2. 빈 생성과 어스팩트 등록 🧩

  1. 빈 생성: 스프링 컨테이너는 HttpRequestLoggingAdvice 빈을 생성합니다.
  2. 어스팩트 등록: @Aspect 어노테이션을 통해 이 빈이 어스팩트로 등록됩니다. 스프링은 이 어스팩트를 감지하여 AOP 설정에 추가합니다.

4.3. DefaultAdvisorAutoProxyCreator의 역할 🤖

  1. DefaultAdvisorAutoProxyCreator 빈 등록: AppConfig 클래스에서 DefaultAdvisorAutoProxyCreator 빈이 정의되어 있습니다.

    @Configuration
    public class AppConfig {
    
        @Bean
        public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
            proxyCreator.setProxyTargetClass(false); // 인터페이스 기반 프록시 사용 (JDK 동적 프록시)
            return proxyCreator;
        }
    }
  2. 빈 후처리기 역할: DefaultAdvisorAutoProxyCreatorBeanPostProcessor로 동작하며, 빈이 초기화된 후에 AOP 어드바이저를 검색하고 프록시를 생성합니다.

4.4. 프록시 생성과 인터셉터 체인 설정 🔗

  1. 어드바이저 검색: DefaultAdvisorAutoProxyCreator는 스프링 컨테이너 내의 모든 어드바이저 빈을 검색합니다. 여기서 HttpRequestLoggingAdvice가 정의한 @Around 어드바이저가 포함됩니다.
  2. 포인트컷 매칭: @HttpRequestLogging 어노테이션이 적용된 클래스의 모든 메서드가 포인트컷과 매칭됩니다.
  3. 프록시 생성 결정: 대상 빈(MyServiceImpl)이 인터페이스(MyService)를 구현하고 있으며, proxyTargetClass=false

로 설정되었기 때문에 JDK 동적 프록시가 생성됩니다.
4. 인터셉터 체인 설정: 매칭된 어드바이저의 어드바이스(LoggingInterceptor)가 인터셉터 체인에 추가됩니다.

4.5. 다이내믹 프록시로의 전환 🔄

프록시 생성 과정에서 Spring AOP는 JDK 동적 프록시를 사용하여 대상 빈을 감싸는 프록시 객체를 생성합니다. 이 프록시는 대상 빈의 인터페이스를 구현하고, InvocationHandler를 통해 메서드 호출을 가로채어 인터셉터 체인을 실행합니다.

  • InvocationHandler: 메서드 호출 시 실제 로직을 실행하기 전에 추가적인 로직(여기서는 로깅)을 실행할 수 있도록 합니다.
  • 다이내믹 프록시의 장점: 런타임에 동적으로 생성되기 때문에, 유연하고 효율적으로 메서드 호출을 가로챌 수 있습니다.

5. AOP의 최적화 방식과 @RestControllerAdvice의 독립성 🧩

5.1. AOP의 최적화 방식

Spring AOP는 프록시 패턴을 기반으로 다양한 최적화 기법을 적용하여 성능을 향상시킵니다. 주요 최적화 방식은 다음과 같습니다:

5.1.1. 싱글턴 InvocationHandler

  • 역할: 프록시가 생성될 때, InvocationHandler를 싱글턴으로 관리하여 메모리 사용량을 줄이고 성능을 최적화합니다.
  • 이점: 여러 프록시 인스턴스가 동일한 InvocationHandler를 공유함으로써 메모리 낭비를 방지합니다.

5.1.2. 인터셉터 체이닝

  • 역할: 여러 개의 어드바이저가 적용될 때, 인터셉터 체인을 구성하여 순차적으로 어드바이스를 실행합니다.
  • 하이브리드 방식: 인터셉터 체인을 통해 다양한 어드바이스를 효율적으로 관리하고, 재귀적으로 실행하여 최적화된 방식으로 로직을 처리합니다.
  • 이점: 인터셉터 체인 내에서 어드바이스들이 순차적으로 실행되며, 중복된 프록시 생성을 방지하고 성능을 향상시킵니다.

5.2. @RestControllerAdvice의 독립성

@RestControllerAdviceSpring MVC의 예외 처리 메커니즘을 활용하여 전역적인 예외 처리를 담당합니다. 이는 Spring AOP의 프록시 기반 메커니즘과는 별개로 동작합니다.

5.2.1. 프록시 미사용

  • 역할: @RestControllerAdvice는 예외 발생 시점에 개입하여 예외를 처리합니다.
  • 동작 방식: DispatcherServletHandlerExceptionResolver를 통해 예외를 감지하고 처리합니다.
  • 독립성: 예외 처리 과정에서 AOP의 프록시 패턴이나 InvocationHandler를 사용하지 않습니다.

5.2.2. 예외 처리 체인의 구성

Spring MVC의 예외 처리 체인은 다음과 같이 구성됩니다:

  1. 컨트롤러 내부의 @ExceptionHandler:

    • 컨트롤러 클래스 내에서 직접 정의된 예외 처리 메서드가 우선적으로 실행됩니다.
  2. @ControllerAdvice 또는 @RestControllerAdvice:

    • 전역 예외 처리기가 존재하면, 이를 통해 예외가 처리됩니다.
  3. 기본 예외 처리기:

    • 그 외의 예외는 Spring MVC의 기본 예외 처리기가 처리합니다.

이 체인은 AOP의 인터셉터 체인과는 별도로 동작하며, 예외 발생 시점에 개입하여 예외를 처리합니다.


이 포스트가 여러분의 Spring AOP와 @RestControllerAdvice에 대한 이해에 도움이 되었길 바랍니다. 😊✨

더 궁금한 점이나 추가적인 설명이 필요하시면 댓글로 남겨주세요! 👍

profile
아자
post-custom-banner

0개의 댓글