본문 바로가기

트러블 슈팅

순환 의존성 문제(circular dependency)

도메인 계층에 User 엔티티 관련 수행해야하는 행위들을 모아놓은 interfac userRepository,

그리고 infrastructure 계층에서 UserRepository를 구현한 UserJpaRepositoryImpl 클래스에서

추상 메서드를 모두 구현한다. UserJpaRepositoryImpl 클래스는 JpaRepository를 상속한

UserJpaRepository 인터페이스를 필드로 갖는데, UserJpaRepository 인터페이스를 구현한

구현체가 UserJpaRepositoryImpl 빈으로 spring이 오인하면서 발생한 순환 의존성 문제가 발생했다.

그래서 UserJpaRepositoryImpl 클래스명을 UserJpaRepositoryAdapter로 변경했더니

순환 의존성 문제가 해결되었다. 아래는 해결과정에 대한 기록을 남겼다.


error:
spring framework에서 발생하는 순환 의존성(circular dependency) 문제
순환 의존성은 두 개 이상의 빈이 서로를 의존할 때 발생하는데,
이 경우 spring이 해당 빈들을 초기화할 수 없어 애플리케이션이 제대로 시작되지 않을 수 있다.
The dependencies of some of the beans in the application context form a cycle:
--> 어플리케이션 컨텍스트에서 몇몇 빈들의 의존성이 순환 구조를 이루고 있다.
--> Spring이 관리하는 IoC 컨테이너에서 몇몇 빈들의 의존 관계가(서로 지목하고 있어)
순환 구조를 형성하고 있다.(순환 참조: circular reference) 순환 참조라는 것은 A가 B를, B가 A를 의존하는 식의 의미다.

그렇다면 어떤 빈들이 순환 구조를 형성하고 있는지 확인해보기

Description:

The dependencies of some of the beans in the application context form a cycle:

   userService defined in file [C:\workspace\daemyung\build\classes\java\main\com\comm\daemyung\applicaiton\service\UserService.class]
┌─────┐
|  userJpaRepositoryImpl defined in file [C:\workspace\daemyung\build\classes\java\main\com\comm\daemyung\infrastructure\repository\UserJpaRepositoryImpl.class]
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.


userService가 userJpaRepositoryImpl을 의존하고 있고,
userJpaRepositoryImpl이 userService를 의존하고 있는 구조인 것을 확인할 수 있었다.
그런데 코드를 봤을 때, userService가 UserRepository를 의존하는 것은 맞고,
interface이기 때문에 동적으로 UserJpaRepositoryImpl이 대입되고,
그럼 UserService가 UserRepositoryImpl을 의존하는 것이 맞는 구조가 됨.
그런데 UserRepositoryImpl은 UserService를 의존하는 것은 아닌데...?
UserJpaRepositoryImpl은 UserJpaRepository interface(의 구현체인 JpaSimpleRepository)를 의존하고 있음.

chat gpt가 알려준 대로 차근차근 해결해보기
[v] UserRepository 인터페이스에 @Repository 어노테이션 붙였는지 확인해보기, 붙이면 안됨.
[v] UserJpaRepositoryImpl외에도 같은 이름의 빈이 존재: UserRepository(domain 계층의 interface), UserJpaRepositoryImpl, UserJpaRepository, 
      UserJpaRepositoryImpl과 UserJpaRepository가 모두 빈으로 올라가며, 스프링이 userRepository 주입시 헷갈릴 수 있다. UserJpaRepositoryImpl 클래스에 @Repository("userJpaRepositoryImpl") 빈의 이름을 명시적으로 적어준다.
[v] application 폴더명이 `application` 으로 되어 있어 오타 수정해봄.

여전히 순환 참조의 문제가 발생하고 있어,
로깅 레벨을 TRACE로 설정하고, 콘솔에 찍힌 것들을 쭉 봤다.

UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in file(UserService 클래스의 경로): Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'userJpaRepositoryImpl' defined file(UserJpaRepositoryImpl 클래스 경로)...
spring이 userService 객체를 생성하려고 시도,
userService는 생성자에 userJpaRepositoryImpl을 필요로 함.
spring이 userJpaRepositoryImpl을 생성하려고 시도,
userJpaRepositoryImpl 객체를 생성하려는데, 자기 자신을 의존하고 있는 참조의 문제가 발생?
userJpaRepositoryImpl이 의존하고 있는 것은 자기 자신이 아니라 interface UserJpaRepository인데?

순환 참조 시나리오 예상
1. userService 빈 생성 시작
2. userJpaRepositoryImpl 빈 생성 요청
3. userJpaRepositoryImpl 빈 생성 시작
4. userJpaRepositoryImpl은 생성자 주입을 위해 UserJpaRepository 타입의 빈을 요청한다.

   private UserJpaRepository userJpaRepository;
   
   public UserJpaRepositoryImpl(UserJpaRepository userJpaRepository) {
   this.userJpaRepository=userJpaRepository;
   }


5. spring은 UserJpaRepository 인터페이스에 해당하는 빈을 찾는데,

이 인터페이스를 구현한 클래스가 UserJpaRepositoryImpl 하나뿐이라고 가정한다??

UserJpaRepository의 구현체는 SimpleJpaRepository여야 한다.

 

그래서 UserJpaRepositoryImpl을 UserJpaRepositoryAdapter로 정정했다.
해결완료.

반응형