TroubleShooting - 스프링 트랜잭션 - 예상치 못한 프록시 주입 문제
TransactionActionExecutor
를 @Autowired
하는 과정에서 Exception
이 발생.
Exception 로그를 보면
“필드에 주입될 normalTransactionActionExecutor
의 타입이 TransactionActionExecutor
인데, 실제 주입된 타입이 'com.sun.proxy.$Proxy79'
타입”
뭔가 Proxy
키워드가 등장한걸 보고 @Transactional
동작 매커니즘과 관련이 있다고 예상함.
기본적으로 @Transactional
은 Spring AOP
를 기반으로 동작하는데, 이 Spring AOP
가 Proxy
기반으로 동작하기 때문에 일련의 과정에서 예외가 발생했다고 판단함.
해결 방법부터 이야기 하자면 두 가지
가 존재
@EnableTransactionManagement(proxyTargetClass = true)
@Autowired
필드의 타입을 상위 타입으로 수정
두 가지 방법은 각각 CGLIB 프록시 사용 명시와 JDK 기본 프록시 사용 유지(?)가 되겠다.
여러 블로그 글을 살펴보니 스프링이 프록시를 다루는 방법은 두 가지다.
- 타겟 클래스가 어떤 인터페이스를 구현할 때
JDK 기본 프록시 사용
- 타겟 클래스가 별도의 인터페이스를 구현하지 않을 때
CGLIB 프록시 사용
위의 Exception
은 결국 타겟 클래스인 TransactionActionExecutor
가 인터페이스인 ActionExecutor
를 구현하기 때문에 JDK 기본 프록시
를 사용하는 과정에서 발생한 것이다.
다만 SpringBoot
에서는 기본 설정이 CGLIB 프록시
라는데 왜 Exception
이 발생했을까?
아무래도 원인은 테스트 환경때문인 것 같다.
조금 디테일하게 얘기하자면 (단지 예상일 뿐이다. 근데 생각보다 논리적인듯)
- 현재 테스트 환경은
@SpringBootTest
- 보통
@SpringBootTest
는@SpringBootApplication
어노테이션을 찾아서ApplicationContext
를 로드하는 방식으로 알고있음 - 로딩 과정에서
@AutoConfiguration
을 함께 동작시키고, 이 때 기본적으로@SpringBootApplication
은@EnableTransactionManagement
어노테이션을 활성화시키게됨. - 그리고 Autoconfiguration 과정에서
@EnableTransactionManagement(proxyTargetClass = true)
로 설정하는 듯 함. - 그래서
SpringBoot
가 기본적으로CGLIB 방식
을 사용한다는 것 같음. - 즉,
AutoConfiguration
과정 자체가SpringBoot
가 제공하고, 이 과정에서CGLIB 프록시 방식
을 채택하는 것 같음
근데 SpringBoot
와 달리 Spring Framework
그 자체로는 기본적으로 CGLIB이 아닌것 처럼 보임
위에서 언급했듯이 타겟 클래스가 인터페이스를 구현한 구현체인지의 여부에 따라 동작하는 것 같음
근데 분명 테스트 환경에서 @SpringBootTest
를 선언했고, 그렇다면 @SpringBootApplication
을 찾아 로딩함으로써 SpringBoot
환경에서 동작해야 하기 때문에 기본적으로 CGLIB을 채택한다면 위와 같은 예외가 발생해선 안되는데?!
⇒ @SpringBootTest
환경이긴 하지만 @SpringBootApplication
이 없기 때문에 직접 로딩할 Configuration
들을 등록했다.
따라서 @EnableTransactionManagement
도 명시적으로 선언해줘야 했고, proxyTargetClass = true로 지정을 해줘야만 CGLIB 방식
을 사용하는 것으로 판단됨.