ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 AOP에서 "프록시를 거친다"는 것은 무슨뜻일까? AOP @Transacitonal 원리
    Spring/Spring Core 2024. 12. 3. 10:32
    728x90
    반응형

    스프링 AOP에서 "프록시를 거친다"는 것은, 스프링이 **프록시 객체(Proxy Object)**를 생성하여 메서드 호출을 가로채고, AOP 로직(Advice)을 실행한 후 실제 객체의 메서드를 호출하는 과정을 의미합니다.

    프록시의 개념

    프록시는 객체를 대신해서 동작하는 중간 대리자 역할을 합니다. 스프링 AOP는 프록시 패턴을 사용하여, 대상 객체의 메서드 호출을 가로채고, AOP로 정의된 부가 작업(예: 로깅, 트랜잭션 관리)을 실행합니다.

     

    프록시가 생성되는 과정

    1. 빈 등록 시 프록시 생성:
      • 스프링 컨테이너는 빈(bean)을 등록할 때, 해당 빈에 AOP가 적용되어야 하는지 확인합니다.
      • AOP 설정이 적용된 경우, 원래의 빈 대신 프록시 객체를 생성합니다.
    2. 프록시 객체의 역할:
      • 프록시는 실제 객체를 감싸고 있으며, 메서드 호출 시 프록시가 호출을 가로챕니다.
      • 프록시는 호출된 메서드의 전후로 AOP 로직(Advice)을 실행한 다음, 실제 객체의 메서드를 호출합니다.
    3. 프록시 생성 방식: 스프링에서 프록시는 다음 두 가지 방식으로 생성됩니다:
      • JDK 동적 프록시: 대상 객체가 인터페이스를 구현한 경우, 해당 인터페이스를 기반으로 프록시를 생성.
      • CGLIB 프록시: 대상 객체가 인터페이스를 구현하지 않았다면, CGLIB 라이브러리를 사용하여 대상 클래스의 서브클래스를 생성.

     

     

    프록시의 동작 구조

    1. 원래의 메서드 호출:
      • 일반적으로 메서드를 호출하면 바로 실제 객체의 메서드가 실행됩니다.
    2. 프록시를 통한 호출:
      • AOP가 적용된 경우, 메서드 호출은 프록시 객체로 전달됩니다.
      • 프록시는 호출을 가로채고, AOP 로직(Advice)을 실행한 후, 실제 객체의 메서드를 호출하거나 호출을 차단합니다.

    다음은 이를 나타내는 간단한 다이어그램입니다:

     

    클라이언트 -> [프록시 객체] -> [실제 대상 객체]
                     ↑
          (AOP 로직: Before, After, Around 등)

     

     

    JDK 동적 프록시의 예

    프록시가 생성되고 호출이 가로채지는 과정을 이해하기 위해, JDK 동적 프록시를 사용하는 간단한 예제를 보겠습니다:

     

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    // 실제 대상 객체
    class Target implements MyInterface {
        @Override
        public void doSomething() {
            System.out.println("실제 작업을 수행합니다.");
        }
    }
    
    // 대상 인터페이스
    interface MyInterface {
        void doSomething();
    }
    
    // 프록시 핸들러
    class MyInvocationHandler implements InvocationHandler {
        private final Object target;
    
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before: 메서드 실행 전 로직");
            Object result = method.invoke(target, args); // 실제 객체의 메서드 호출
            System.out.println("After: 메서드 실행 후 로직");
            return result;
        }
    }
    
    public class ProxyExample {
        public static void main(String[] args) {
            Target target = new Target();
            MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new MyInvocationHandler(target)
            );
    
            proxy.doSomething(); // 프록시를 통해 호출
        }
    }

     

     

    스프링 AOP에서 프록시 사용 예시

    스프링에서 AOP를 사용하는 경우, 다음과 같은 방식으로 프록시가 생성됩니다:

    1. Aspect 정의:
    @Aspect
    @Component
    public class LoggingAspect {
    
        @Before("execution(* com.example.service.MyService.*(..))")
        public void logBefore() {
            System.out.println("Before: 메서드 실행 전 로직");
        }
    
        @After("execution(* com.example.service.MyService.*(..))")
        public void logAfter() {
            System.out.println("After: 메서드 실행 후 로직");
        }
    }

     

    대상 클래스:

     

    @Component
    public class MyService {
        public void doSomething() {
            System.out.println("실제 작업을 수행합니다.");
        }
    }

     

    스프링 컨텍스트 설정:

     

    @EnableAspectJAutoProxy
    @Configuration
    public class AppConfig {
    }

     

    실행 결과:

     

    ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService service = context.getBean(MyService.class);
    
    service.doSomething();

     

    출력:

    Before: 메서드 실행 전 로직
    실제 작업을 수행합니다.
    After: 메서드 실행 후 로직

     

     

    1. @Transactional의 동작 원리

    스프링에서 @Transactional은 AOP를 기반으로 동작하며, 실제로는 다음 과정을 거칩니다:

    1. 빈 등록 시 프록시 생성:
      • @Transactional이 있는 클래스(또는 메서드)를 스프링 컨테이너에 등록할 때, 스프링은 프록시 객체를 생성합니다.
      • 프록시는 트랜잭션 관련 로직을 감싼 래퍼(wrapper) 역할을 합니다.
    2. 프록시 호출 시 AOP 적용:
      • 컨트롤러에서 해당 서비스 메서드를 호출하면, 실제 객체 대신 프록시 객체가 호출됩니다.
      • 프록시는 AOP 어드바이스(Advice)를 실행하고, 트랜잭션을 시작하거나 롤백하는 등의 작업을 수행합니다.

     

    2. 스프링의 핵심 클래스와 메커니즘

    스프링이 @Transactional을 처리하기 위해 사용하는 주요 클래스와 메커니즘은 다음과 같습니다:

    1. AbstractAutoProxyCreator:
      • 스프링이 빈을 초기화할 때 프록시를 생성하는 클래스.
      • 빈이 트랜잭션과 같은 어드바이스가 필요하면, 이를 프록시로 감쌉니다.
    2. TransactionInterceptor:
      • 트랜잭션 어드바이스를 처리하는 클래스.
      • 메서드 실행 전후로 트랜잭션 시작/커밋/롤백 등을 처리합니다.
    3. PlatformTransactionManager:
      • 트랜잭션 관리의 실제 구현체. 예: DataSourceTransactionManager.

    3. 구체적인 동작 과정

    컨트롤러 → 서비스 호출 → AOP 적용 → 실제 서비스 메서드 호출의 전체 과정:

    코드 구성:

    1. 컨트롤러

    @RestController
    public class MyController {
    
        @Autowired
        private MyService myService;
    
        @GetMapping("/test")
        public String test() {
            myService.processTransaction();
            return "Transaction Complete";
        }
    }

     

    2. 서비스

     

    @Service
    public class MyService {
    
        @Transactional
        public void processTransaction() {
            System.out.println("Executing Service Logic...");
            // 비즈니스 로직
        }
    }

     

    실제 동작 과정과 코드 흐름:

    1. 빈 등록 시 프록시 생성:
      • 스프링은 @Transactional이 붙은 MyService를 빈으로 등록할 때, ProxyFactory를 사용하여 프록시 객체를 생성합니다.
      • 이 작업은 AbstractAutoProxyCreator에서 수행됩니다.

    관련 코드 (추상화된 형태):

     

    public Object wrapIfNecessary(Object bean, String beanName) {
        if (bean에 AOP가 필요하다면) {
            ProxyFactory proxyFactory = new ProxyFactory(bean);
            proxyFactory.addAdvice(new TransactionInterceptor());
            return proxyFactory.getProxy();
        }
        return bean;
    }

     

     

    • 컨트롤러에서 서비스 호출:
      • 컨트롤러가 myService.processTransaction()을 호출하면, 실제로는 서비스 프록시 객체가 호출됩니다.
    • 프록시에서 어드바이스 실행:
      • 프록시는 TransactionInterceptor를 호출하여 트랜잭션을 시작합니다.
    public Object invoke(MethodInvocation invocation) throws Throwable {
        TransactionStatus status = transactionManager.getTransaction(); // 트랜잭션 시작
        try {
            Object result = invocation.proceed(); // 실제 메서드 호출
            transactionManager.commit(status); // 성공 시 커밋
            return result;
        } catch (Throwable ex) {
            transactionManager.rollback(status); // 실패 시 롤백
            throw ex;
        }
    }

     

     

    1. 실제 서비스 메서드 실행:
      • 트랜잭션이 시작된 상태에서 실제 processTransaction() 메서드가 실행됩니다.
    2. 트랜잭션 종료:
      • 메서드 실행이 끝나면, 프록시는 트랜잭션을 커밋하거나 롤백합니다.

    스프링 프레임워크의 실제 코드:

    스프링 프레임워크의 내부 코드에서 위 과정이 어떻게 구현되어 있는지 살펴보겠습니다.

    1. AbstractAutoProxyCreator (프록시 생성)

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (bean에 AOP가 필요하면) {
            ProxyFactory proxyFactory = new ProxyFactory();
            proxyFactory.setTarget(bean);
            proxyFactory.addAdvice(transactionInterceptor); // 트랜잭션 어드바이스 추가
            return proxyFactory.getProxy();
        }
        return bean;
    }

     

    2. TransactionInterceptor (AOP 로직 실행)

    public Object invoke(MethodInvocation invocation) throws Throwable {
        TransactionInfo txInfo = createTransactionIfNecessary(invocation.getMethod(), invocation.getThis());
        try {
            Object result = invocation.proceed(); // 실제 메서드 호출
            commitTransactionAfterReturning(txInfo); // 트랜잭션 커밋
            return result;
        } catch (Throwable ex) {
            completeTransactionAfterThrowing(txInfo, ex); // 트랜잭션 롤백
            throw ex;
        }
    }

     

    3. PlatformTransactionManager (트랜잭션 관리)

     

    public TransactionStatus getTransaction(TransactionDefinition definition) {
        // 트랜잭션 시작
    }
    
    public void commit(TransactionStatus status) {
        // 트랜잭션 커밋
    }
    
    public void rollback(TransactionStatus status) {
        // 트랜잭션 롤백
    }

     

    4. 결론

    • 프록시 생성 시점:
      • 서비스 빈이 등록될 때(AbstractAutoProxyCreator) 프록시 객체가 생성되고 트랜잭션 어드바이스가 부착됩니다.
    • 프록시 호출 시점:
      • 컨트롤러에서 서비스 메서드 호출 → 프록시 객체가 호출됨 → TransactionInterceptor가 실행.
    • 트랜잭션 처리:
      • TransactionInterceptor는 PlatformTransactionManager를 사용하여 트랜잭션을 시작/커밋/롤백합니다.
    728x90
    반응형
Designed by Tistory.