Spring AOP(Aspect-Oriented Programming)에서 같은 객체 안의 메서드가 this로 다른 메서드를 부르면, 그 메서드에 붙은 부가 기능(@Transactional, @Async, @Cacheable 등)이 실행되지 않는 현상. 부가 기능은 프록시가 실행하는데, 내부 호출은 프록시를 거치지 않기 때문이다.
@Service
public class OrderService {
public void placeAll(List<Order> orders) {
orders.forEach(this::place); // this = 실제 객체, 프록시가 아님
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void place(Order order) { /* 새 트랜잭션이 열리지 않는다 */ }
}컨트롤러 → [프록시] → OrderService.placeAll()
└ this.place() ← 프록시를 건너뜀밖에서 주입받은 빈(bean)은 프록시이므로 orderService.place()는 정상 동작한다. 같은 메서드를 내부에서 부를 때만 부가 기능이 조용히 빠지고 오류도 나지 않아 찾기 어렵다. @Transactional이 적용되지 않는 흔한 이유다.
해결
Spring 문서가 권하는 순서대로:
- 내부 호출을 없앤다(권장): 부가 기능이 필요한 메서드를 다른 빈으로 옮기고 그 빈을 주입받아 부른다. 일이 조금 들지만 가장 덜 침습적이다
- 자기 자신을 주입한다(self injection): 자기 빈 참조(프록시)를 주입받아
this대신 그 참조로 부른다. 자기 자신에 대한 순환 참조이므로 생성자 주입이나 순환 참조를 막은 설정에서는@Lazy로 늦춰 주입해야 할 수 있다. Spring 문서는 이것도 최후의 수단으로 보고 별도 빈 분리를 먼저 권한다 AopContext.currentProxy(): 현재 프록시를 꺼내 부른다. 코드가 Spring AOP에 강하게 묶이고 프록시 노출(exposeProxy) 설정이 필요해서 Spring 문서는 가장 권하지 않는다
AspectJ 컴파일 시점·로드 시점 위빙(weaving)은 프록시가 아니라 바이트코드에 부가 기능을 넣으므로 이 문제가 없다. @Transactional도 @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)로 기본인 프록시 모드 대신 AspectJ 모드를 쓰면(spring-aspects와 위빙 설정 필요) 내부 호출에도 트랜잭션이 적용된다. AOP의 개념 자체는 스프링 AOP에 있다.
출처: Spring Framework — Understanding AOP Proxies · Spring Framework — Self Injection · Spring Framework — Using @Transactional