본문 바로가기

Back-End/Spring

Self-Invocation(자기호출) 문제

현재 결제 프로세스를 진행중인데 @Transactional과 @Retryable을 동시에 사용하는 재시도 로직이 있었습니다.
하지만 @Transactional과 @Retryable이 적용이 되지 않는 문제가 발생했습니다.

Expected Result

 

Actual Result
✅ 
하지만 기대와 달리 @Retryable이 작동하지 않는 이슈가 발생했습니다.


테스트 케이스 작성

그래서 저는 총 7가지 테스트 케이스를 만들어 @Transactional@Retryable이 어떻게 동작하고 어디서 수행되지 않는지 확인을 했습니다.

@Retryable 단독 테스트(Case 1~3)

케이스 설명 결고
Case1 @Retryable 이 적용된 메서드 단독 실행 성공
Case2 같은 클래스 내부에서 일반 메서드가 @Retryable 메서드 호츌 실패
Case3 일반 메서드에서 다른 클래스의 @Retryable 메서드 호출 성공

@Transactional + @Retryable 조합 테스트 (Case 4~7)

케이스 설명 결과
Case4 @Transactional 이 적용된 메서드에서 같은 클래스의 @Retryable 메서드 호출 실패
Case5 같은 클래스 내에서 @Retryable 과 @Transactional 을 분리하여 호출 실패
Case6 @Transactional 이 적용된 메서드에서 다른 클래스의 @Retryable 메서드 호출 성공
Case7 일반 메서드에서 같은 클래스의 @Transactional 메서드와 다른 클래스 @Retryable 메서드 호출 성공

✅ 테스트 케이스 결과 분석

같은 클래스 내부에서 호출을 한다면 동작하지 않고 다른 클래스에서 주입을 받아 호출을한다면 정상적으로 동작한다는 것을 알 수 있었습니다.


원인 분석 : Self-Invocation

그래서 테스트 케이스 결과 분석을 통해, "왜 다른 클래스에서 주입 받았을 경우에만 성공했을까?" 고민을 하였습니다.
IntelliJ 의 경고를 힌트삼아, 구글링을 통해 알아본 결과 Self-Invocation 문제였다는 것을 알 수 있었습니다.

Self-Invocation 이란?

"Spring AOP의 Proxy를 거치지 않고, 클래스 내부에서 자기 자신의 메서드를 직접 호출 하는 것" 을 의미합니다.

Self-Invocation에 대해 더 자세히 설명을 하자면

@Transactional, @Retryable 등과 같은 AOP Annotation 은 프록시 객체를 통해 호출될 때만 동작을 합니다.
Spring 컨테이너(ApplicationContext)가 Bean을 생성할 때, AOP 프록시를 적용하여 해당 Bean을 등록합니다.
그 결과, ApplicationContext에서 Bean을 주입받거나 getBean()을 호출하면 AOP 프록시가 적용된 객체를 받게 됩니다.
그래서 자기 자신을 주입 받을 때도 ApplicationContext 를 거쳐 Proxy 객체를 주입 받기 때문에 정상적으로 동작이 가능했습니다.
하지만 동일 클래스 내부에서 직접 자신의 메서드를 호출하게 된다면 프록시 객체를 거치지 않고 원래 객체 메서드를 호출하게 되어 AOP 어노테이션 기능이 적용되지 않게 됩니다.
이 현상을 Self-invocation이라고 하며, Spring Proxy 를 거치지 않은 객체는 Annotation 기능이 적용되지 않았던 것입니다.

 


해결 방법

  • Case 1: ApplicationContext 에서 자기 자신을 주입 받기
  • Case 2: @Retryable과 @Transactional이 적용된 메서드를 외부에서 주입받아 사용하기