[Spring Boot] 순환 참조(Circular References) 에러 해결

enjoy89·2024년 1월 10일
0
post-custom-banner

오류 내용

코디룩 서비스 클래스가 아이템 서비스 클래스의 빈을 주입 받고, 아이템 서비스 클래스가 코디룩 서비스 클래스의 빈을 주입 받으면서 서로 순환 참조 에러가 발생했다.



문제 상황

CoordinateLookAdminServiceImpl.class

public class CoordinateLookAdminServiceImpl implements CoordinateLookAdminService {

    private final CoordinateLookRepository coordinateLookRepository;
    private final StylePointAdminService stylePointAdminService;
    private final ItemAdminService itemAdminService;

ItemAdminServiceImpl.class

public class ItemAdminServiceImpl implements ItemAdminService {

    private final ItemRepository itemRepository;
    private final BrandAdminService brandAdminService;
    private final CategoryService categoryService;

	// 생성자 주입 방식 -> 에러 발생 원인
	private final CoordinateLookAdminService coordinateLookAdminService;

	// 아래와 같이 coordinateLookAdminService를 생성자 주입 방식으로 가져온 후 사용 -> 순환 참조 에러 발생 원인
    @Override
    public List<Item> findAllByCoordinateLookId(Long id) {
        CoordinateLook coordinateLook = coordinateLookAdminService.findById(id);
        return itemRepository.findItemByCoordinateLook(coordinateLook);
    }
  • 위처럼 CoordinateLookAdminServiceImpl 클래스에서 ItemAdminServiceImpl 의존 관계 주입을 하고 있고, 반대로 ItemAdminServiceImpl 클래스에서 CoordinateLookAdminServiceImpl 의존 관계 주입을 하고 있다.
  • 둘 다 생성자 주입 방식을 사용하고 있으며, 생성자 주입 방식은 객체가 생성될 때 모든 의존성이 주입된다.
  • 또한, findAllByCoordinateLookId 메서드에서 코디룩 서비스를 사용하므로 순환 참조 에러가 발생한 것이다.
  • 순환 참조 에러는 스프링 컨테이너에서 빈을 초기화할 때 발생한다. 여러 클래스 간의 의존성이 순환 참조로 인해 해결할 수 없게 되면 스프링은 해당 에러를 발생시킨다.



해결 방법

의존성 주입 방식 변경

  • 생성자 주입(Constructor Injection)방식 대신 메서드 주입(Method Injection) 또는 필드 주입(Field Injection)을 방식 사용하여, 의존성을 느슨하게 유지하도록 한다.
  • 메서드 주입 방식은 객체 생성 시 모든 의존성을 주입하는 생성자 주입과는 달리, 특정 메서드가 호출될 때만 의존성이 주입된다.
  • 이는 특정 동작에 필요한 의존성만 주입하여 리소스를 더 효율적으로 사용할 수 있게 해준다.
  • 이를 통해 생성자 주입 방식보다 더 느슨한 결합(Loose Coupling)을 얻을 수 있다.
  • 느슨한 결합이란, 의존성을 최소화하고 변경에 대한 유연성을 높이는 것을 의미한다.




수정 된 ItemAdminServiceImpl.class

public class ItemAdminServiceImpl implements ItemAdminService {

    private final ItemRepository itemRepository;
    private final BrandAdminService brandAdminService;
    private final CategoryService categoryService;

	// 아래와 같이 coordinateLookAdminService를 생성자 주입 방식 대신, 메서드 주입 방식으로 변경
    @Override
    public List<Item> findAllByCoordinateLookId(Long id, CoordinateLookAdminService coordinateLookAdminService) {
        CoordinateLook coordinateLook = coordinateLookAdminService.findById(id);
        return itemRepository.findItemByCoordinateLook(coordinateLook);
    }

수정 된 ItemAdminController.class

public class ItemAdminController {

    private final ItemAdminService itemAdminService;
    
	// Controller에서 서비스 객체 생성자 주입 추가
    private final CoordinateLookAdminService coordinateLookAdminService;

    @GetMapping("/coordinate-looks/{id}")
    public ResponseEntity<List<ItemDto>> getItemsByCoordinateLookId(@PathVariable Long id) {

		// coordinateLookAdminService를 파라미터로 전달
        List<Item> items = itemAdminService.findAllByCoordinateLookId(id, coordinateLookAdminService);

        if (items.isEmpty()) {
            return ResponseEntity.notFound().build();
        }

        List<ItemDto> itemResponseDtos = items.stream()
                .map(itemAdminService::convertToDto)
                .collect(Collectors.toList());

        return ResponseEntity.ok(itemResponseDtos);
    }

}
profile
Backend Developer 💻 😺
post-custom-banner

0개의 댓글