TroubleShooting - 스프링 트랜잭션 - 예상치 못한 프록시 주입 문제


TransactionActionExecutor@Autowired 하는 과정에서 Exception이 발생.

Exception 로그를 보면

필드에 주입될 normalTransactionActionExecutor의 타입이 TransactionActionExecutor인데, 실제 주입된 타입이 'com.sun.proxy.$Proxy79' 타입”

뭔가 Proxy 키워드가 등장한걸 보고 @Transactional 동작 매커니즘과 관련이 있다고 예상함.

기본적으로 @TransactionalSpring AOP를 기반으로 동작하는데, 이 Spring AOPProxy 기반으로 동작하기 때문에 일련의 과정에서 예외가 발생했다고 판단함.


해결 방법부터 이야기 하자면 두 가지가 존재

  • @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 방식을 사용하는 것으로 판단됨.

참고