"Self Invocation"은 개념상으로는 자기 자신을 호출하는 것을 의미한다.
AOP에서 Self Invocation 문제란?
객체 소속에 메서드에서 자기 자신의 메서드를 호출했을 때 AOP가 동작하지 않는 현상을 의미한다.
원인
Spring AOP는 프록시 기반으로 동작하게 됩니다.
AOP 대상의 객체는 DI하게 될 때 실제 객체가 아닌 프록시 객체가 주입 되게 됩니다.
객체 내에서 해당 객체 메서드를 호출하게 되면 프록시 객체에 의해 호출되는 것이 아닌 직접호출로 이루어지게 되어 AOP가 동작하지 않게 됩니다.
예제
- AOP 동작 시킬 어노테이션
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface AopGoGo {
}
- SampleAop 클래스
@Slf4j
@Aspect
@Component
public class SampleAop {
@Around("@annotation(AopGoGo)")
public Object aop1번(ProceedingJoinPoint pjp) throws Throwable {
log.error("{} 으로 부터 탄생이 되었다.", pjp.getSignature().getName());
final Object proceed = pjp.proceed();
return proceed;
}
}
- 서비스
@Slf4j
@Service
public class BusinessService {
@AopGoGo
public void oneStep() {
log.error("oneStep 콜");
// 해당 메서드에서 AOP가 동작 하지 않게 됩니다.
this.twoStep();
}
@AopGoGo
public void twoStep() {
log.error("twoStep 콜");
}
}
BusinessService.oneStep을 호출했을 때 예상 결과는
SampleAop -> BusinessService.oneStep -> SampleAop -> BusinessService.twoStep 으로
되어야 하는 것처럼 보이지만 결과는 아래와 같습니다.
[ 결과 (BusinessService.oneStep을 호출한 결과) ]
> SampleAop -> BusinessService.oneStep -> BusinessService.twoStep
왜 그럴까?
호출부 - 호출부에서는 다음과 같이 프록시 객체인것을 확인하였고
내부 호출부 프록시 객체가 아닌 실제 서비스객체가 참조하고 있음을 알 수 있습니다.
그로 인하여 AOP가 동작하지 않은 것을 확인할 수 있습니다.
프록시 객체가 아닌 실제 서비스객체가 참조하고 있음을 알 수 있습니다.
그로 인하여 AOP가 동작하지 않은 것을 확인할 수 있습니다.
그럼 어떻게 동작할 수 있겠 끔 할 수 있을까?
가장 간단한 방법으로는 아래와 같이 직접 프록시를 호출하는 방법이 있겠다.
//프록시 에서 호출하기
((BusinessService) (AopContext.currentProxy())).twoStep();
조금만 더 생각하면 자기 자신을 DI 하는 방법도 있겠지만 이는 순환 참조를 유발하는 행위라 별로 권장하지 않는 것 같습니다.
다른 방법으로는 해당 블로그를 참조하시면 될 거 같습니다.
https://gmoon92.github.io/spring/aop/2019/04/01/spring-aop-mechanism-with-self-invocation.html
결론
- AOP 는 프록시 객체를 이용하여 동작함으로 자기 객체에 있는 메서드를 호출할 때는 프락시객체를 활요하는게 아닌 직접 호출으로 되어 동작하지 않게 된다.
- 이를 해결하기 위한 방법으로는 프록시객체를 호출하는 방법, 자기 자신을 DI 하는 방법, 라이브러리의 옵션값을 수정하는 방법이 있겠습니다.
- AOP 기반으로 동작하는 @Tranctional 어노테이션 사용할 때 유의하여 사용해야 할 것입니다.
개인적인 결론
근본적으로 왜 하나의 계층에서 내부 메서드를 호출하게 되었을까에 대해서 고민할 필요가 있다고 생각이 됩니다.
이는 객체 분리가 잘 되어있지 않음을 의미하고 계층을 나눈 의미가 없지 않을까 하는 생각이 되어 위의 해결법보다는 구조를 개선하는 게 좋아 보입니다.