
문제
현재 진행 중인 프로젝트에서 'PostsService'와 'ParticipationDetailsService' 간에 순환 참조 문제가 발생했습니다.
코드는 다음과 같습니다.
PostsService.class
@RequiredArgsConstructor
@Transactional
@Service
public class PostsService {
private final UsersService usersService;
private final ParticipationDetailsService participationDetailsService;
private final UsersRepository usersRepository;
private final PostsRepository postsRepository;
private final ParticipationDetailsRepository participationDetailsRepository;
public Long save(String accessToken, PostsSaveRequestDto requestDto, Long roomId) {
Users user = usersService.getUserByToken(accessToken);
Posts post = requestDto.toEntity(user);
Long postId = postsRepository.save(post).getId();
participationDetailsService.join(accessToken, postId);
return postId;
}
...
}
ParticipationDetailsService.class
@RequiredArgsConstructor
@Transactional
@Service
public class ParticipationDetailsService {
//생성자 주입, 에러 발생 원인
private final PostsService postsService;
private final UsersService usersService;
private final PostsRepository postsRepository;
private final ParticipationDetailsRepository participationDetailsRepository;
public boolean cancelParticipation(Long postId, String accessToken) {
Users user = usersService.getUserByToken(accessToken);
Posts post = postsRepository.findById(postId).orElseThrow(()
-> new IllegalArgumentException("해당 모집글이 없습니다. id=" + postId));
ParticipationDetails participationDetails =
participationDetailsRepository.findParticipationDetailsByUserAndPost(user, post)
.orElseThrow(() -> new IllegalArgumentException("해당 참가내역이 없습니다."));
postsService.cancelParticipation(post.getId());
participationDetailsRepository.delete(participationDetails);
return true;
}
...
}
코드를 보면 'PostsService'에서 생성자 주입을 통해 'ParticipationDetailsService'를 의존성으로 받고 있으며, 'ParticipationDetailsService' 역시 생성자 주입을 통해 'PostsService'를 의존성으로 받고 있습니다.
이로 인해 애플리케이션 구동 시, 스프링 컨테이너(IOC)는 'PostsService' 빈을 생성하려고 할 때 'ParticipationDetailsService'가 필요하고, 'ParticipationDetailsService' 빈을 생성하려고 할 때는 'PostsService'가 필요하게 됩니다. 이 과정에서 두 빈을 서로 주입하기 위해 계속해서 상대방 빈을 찾는 무한 루프가 발생합니다.
결과적으로, 스프링은 어떤 빈을 먼저 생성해야 할지 결정할 수 없는 상황에 빠지게 되며, 이를 "순환 참조" 문제라고 합니다.
해결 방법
메서드 주입 방식을 사용하여 해결하였습니다.
메서드 주입: 주입해야 하는 의존성을 해당 메서드의 파라미터로 받아 처리하는 방식
수정 된 ParticipationDetailsService.class
@RequiredArgsConstructor
@Transactional
@Service
public class ParticipationDetailsService {
private final UsersService usersService;
private final PostsRepository postsRepository;
private final ParticipationDetailsRepository participationDetailsRepository;
public boolean cancelParticipation(Long postId, String accessToken, PostsService postsService) {
Users user = usersService.getUserByToken(accessToken);
Posts post = postsRepository.findById(postId).orElseThrow(()
-> new IllegalArgumentException("해당 모집글이 없습니다. id=" + postId));
ParticipationDetails participationDetails =
participationDetailsRepository.findParticipationDetailsByUserAndPost(user, post)
.orElseThrow(() -> new IllegalArgumentException("해당 참가내역이 없습니다."));
postsService.cancelParticipation(post.getId());
participationDetailsRepository.delete(participationDetails);
return true;
}
문제는 해결했지만, 메서드 주입 방식이 최선은 아닙니다.
메서드 주입을 사용하면 의존성 주입을 위해 Spring Container가 필요하게 되므로, 순수 자바 코드로 객체를 생성하여 테스트하는 것은 어렵습니다.
또한, 메서드 주입을 통해 의존성을 나중에 주입할 수 있기 때문에, 객체가 생성된 후에도 의존성이 변경될 수 있습니다.
이는 객체의 불변성을 해칠 수 있으며, 안정성을 보장하기 어렵습니다.
따라서, 가장 좋은 방법은 참조 관계를 다시 설계하는 것입니다!
'트러블슈팅' 카테고리의 다른 글
TimeZone 불일치 문제 해결하기(UTC, KST) (0) | 2024.08.31 |
---|---|
Signature expired: is now earlier than 에러 (0) | 2024.05.08 |
CRLF, LF? (0) | 2024.03.09 |
구글 서비스 버전 충돌 문제 해결하기 (0) | 2024.01.27 |
H2 DB "start_value" 에러 (1) | 2023.11.23 |
느리더라도 단단하게 성장하고자 합니다!
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!