-
스프링 AOP에서 "프록시를 거친다"는 것은 무슨뜻일까? AOP @Transacitonal 원리Spring/Spring Core 2024. 12. 3. 10:32728x90반응형
스프링 AOP에서 "프록시를 거친다"는 것은, 스프링이 **프록시 객체(Proxy Object)**를 생성하여 메서드 호출을 가로채고, AOP 로직(Advice)을 실행한 후 실제 객체의 메서드를 호출하는 과정을 의미합니다.
프록시의 개념
프록시는 객체를 대신해서 동작하는 중간 대리자 역할을 합니다. 스프링 AOP는 프록시 패턴을 사용하여, 대상 객체의 메서드 호출을 가로채고, AOP로 정의된 부가 작업(예: 로깅, 트랜잭션 관리)을 실행합니다.
프록시가 생성되는 과정
- 빈 등록 시 프록시 생성:
- 스프링 컨테이너는 빈(bean)을 등록할 때, 해당 빈에 AOP가 적용되어야 하는지 확인합니다.
- AOP 설정이 적용된 경우, 원래의 빈 대신 프록시 객체를 생성합니다.
- 프록시 객체의 역할:
- 프록시는 실제 객체를 감싸고 있으며, 메서드 호출 시 프록시가 호출을 가로챕니다.
- 프록시는 호출된 메서드의 전후로 AOP 로직(Advice)을 실행한 다음, 실제 객체의 메서드를 호출합니다.
- 프록시 생성 방식: 스프링에서 프록시는 다음 두 가지 방식으로 생성됩니다:
- JDK 동적 프록시: 대상 객체가 인터페이스를 구현한 경우, 해당 인터페이스를 기반으로 프록시를 생성.
- CGLIB 프록시: 대상 객체가 인터페이스를 구현하지 않았다면, CGLIB 라이브러리를 사용하여 대상 클래스의 서브클래스를 생성.
프록시의 동작 구조
- 원래의 메서드 호출:
- 일반적으로 메서드를 호출하면 바로 실제 객체의 메서드가 실행됩니다.
- 프록시를 통한 호출:
- 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를 사용하는 경우, 다음과 같은 방식으로 프록시가 생성됩니다:
- 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를 기반으로 동작하며, 실제로는 다음 과정을 거칩니다:
- 빈 등록 시 프록시 생성:
- @Transactional이 있는 클래스(또는 메서드)를 스프링 컨테이너에 등록할 때, 스프링은 프록시 객체를 생성합니다.
- 프록시는 트랜잭션 관련 로직을 감싼 래퍼(wrapper) 역할을 합니다.
- 프록시 호출 시 AOP 적용:
- 컨트롤러에서 해당 서비스 메서드를 호출하면, 실제 객체 대신 프록시 객체가 호출됩니다.
- 프록시는 AOP 어드바이스(Advice)를 실행하고, 트랜잭션을 시작하거나 롤백하는 등의 작업을 수행합니다.
2. 스프링의 핵심 클래스와 메커니즘
스프링이 @Transactional을 처리하기 위해 사용하는 주요 클래스와 메커니즘은 다음과 같습니다:
- AbstractAutoProxyCreator:
- 스프링이 빈을 초기화할 때 프록시를 생성하는 클래스.
- 빈이 트랜잭션과 같은 어드바이스가 필요하면, 이를 프록시로 감쌉니다.
- TransactionInterceptor:
- 트랜잭션 어드바이스를 처리하는 클래스.
- 메서드 실행 전후로 트랜잭션 시작/커밋/롤백 등을 처리합니다.
- 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..."); // 비즈니스 로직 } }
실제 동작 과정과 코드 흐름:
- 빈 등록 시 프록시 생성:
- 스프링은 @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; } }
- 실제 서비스 메서드 실행:
- 트랜잭션이 시작된 상태에서 실제 processTransaction() 메서드가 실행됩니다.
- 트랜잭션 종료:
- 메서드 실행이 끝나면, 프록시는 트랜잭션을 커밋하거나 롤백합니다.
스프링 프레임워크의 실제 코드:
스프링 프레임워크의 내부 코드에서 위 과정이 어떻게 구현되어 있는지 살펴보겠습니다.
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반응형'Spring > Spring Core' 카테고리의 다른 글
- 빈 등록 시 프록시 생성: