2020-05-05 11:01:07.146 WARN 24213 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accountController' defined in file [/Users/seongjin/Documents/taggare/server/out/production/classes/com/sns/server/account/AccountController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accountService' defined in file [/Users/seongjin/Documents/taggare/server/out/production/classes/com/sns/server/account/AccountService.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig' defined in file [/Users/seongjin/Documents/taggare/server/out/production/classes/com/sns/server/security/SecurityConfig.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'formLoginAuthenticationProvider': Unsatisfied dependency expressed through field 'accountService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'accountService': Requested bean is currently in creation: Is there an unresolvable circular reference? *************************** APPLICATION FAILED TO START *************************** Description: The dependencies of some of the beans in the application context form a cycle: accountController defined in file [/Users/seongjin/Documents/taggare/server/out/production/classes/com/sns/server/account/AccountController.class] ┌─────┐ | accountService defined in file [/Users/seongjin/Documents/taggare/server/out/production/classes/com/sns/server/account/AccountService.class] ↑ ↓ | securityConfig defined in file [/Users/seongjin/Documents/taggare/server/out/production/classes/com/sns/server/security/SecurityConfig.class] ↑ ↓ | formLoginAuthenticationProvider (field private com.sns.server.account.AccountService com.sns.server.security.providers.FormLoginAuthenticationProvider.accountService) └─────┘ 애플리케이션 컨텍스트에서 일부 Bean의 종속성이 순환주기를 형성하는 문제가 발생함. Show 순환 종속성 또는 순환 참조 문제는 둘 이상의 Bean이 생성자를 통해 서로를 주입하려고할 때 발생한다. accountService (1) → securityConfig (2) → formLoginAuthenticationProvider (3) 어플리케이션이 실행하면, 스프링 컨테이너는 3 → 2 →1 순으로 객체를 생성한다. 하지만 1 → 2 →3 순으로 객체들이 의존하기 때문에 어떤 객체를 먼저 생성해야하는지 문제가 발생한다. 이 경우 Spring은 컨텍스트를로드하는 동안 BeanCurrentlyInCreationException을 발생시킨다. 임시방편 해결 방법FormLoginAuthenticationProvider 보통 의존성 주입은 @Autowired 또는, @RequiredArgsConstructor을 통한 주입을 할 것이다. @Component @RequiredArgsConstructor public class FormLoginAuthenticationProvider implements AuthenticationProvider { private final AccountService accountService; private final PasswordEncoder passwordEncoder; ... }일반적으로 Spring은 애플리케이션 컨텍스트의 시작 / 부트스트랩(일반적으로 한 번 시작되면 알아서 진행되는 일련의 과정) 과정에서 모든 싱글톤 Bean을 즉시 생성한다. 그 이유는 런타임이 아닌 컴파일 과정에서 모든 가능한 오류를 즉시 피하고 감지하는 것이다. 위처럼 문제가 되는 빈들을 주입 받을 때, @Lazy 어노테이션을 통해 지연로딩으로 임시방편으로 문제를 해결한다. 어플리케이션이 실행하는 즉시 초기화가 아닌 필요에 따라 지연 해결 프록시(lazy-resolution proxy)가 주입되는 방식으로 사용한다. 결론임시방편으로 @Lazy 어노테이션을 통한 지연로딩으로 순환참조 문제를 해결했지만, 가장 이상적인건 스프링 빈들의 관계를 재설계해서 문제를 해결하는 것이다. 참고 https://www.baeldung.com/circular-dependencies-in-spring https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Lazy.html 스프링 부트부터 접한 스프링 알못이라 스프링에 대해 공부를 하다보니 너무나 모르고 있는 게 많아서 정리해봤다. 어노테이션 없이 빈 설정스프링이 관리하는 객체인 빈으로 생성하기 위해서 아래와 같은 어노테이션이 필수인 줄 알았다. 하지만 직접 코딩을 해보니 이 생각은 거짓이었다. 우선 느슨한 결합을 위해 인터페이스를 하나 선언한다.
인터페이스의 구현체도 하나 만들어준다.
해당 구현체를 의존성으로 갖는 다른 구현체도 만들어보자.
이제 CoupangOrderService 빈이 제대로 생성되는지 테스트 코드를 작성해보자. (JUnit5를 사용하였다.)
위 테스트를 실행하면 circular reference(순환 참조) 때문에 빈을 생성할 수 없는 오류가 난다. CoupangOrderService는 OrderService 인터페이스를 의존성으로 받는데 그 구현체가 CoupangOrderService 자신 밖에 없기 때문이다. 그럼 ApplicationContext가 OrderService의 다른 구현체인 TimonOrderService까지 알게 해주자.
이제 테스트는 성공한다. 한 번 위 가설이 맞는지 검증해보자.
이제 테스트를 돌려보면 아래와 같이 CoupangOrderService에 OrderService를 주입하는데 TimonOrderService를 주입해야할지, WeMakePriceOrderService를 주입해야할지 모른다는 오류가 나온다.
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'OrderService' available: expected single matching bean but found 2: timonOrderService,weMakePriceOrderService OrderService의 구현체는 세 개인데 당연스레 본인(CoupangOrderService)는 빼고 의존성 주입을 시도한 것이다.
OrderService의 빈은 2개(CoupangOrderService 본인을 제외하고)라서 빈의 이름으로 검색을해야하는데 orderSe라는 이름의 빈은 없기 때문이다. 이제 테스트가 성공하게 제대로 된 빈의 이름으로 바꿔주자.
빈 자동 스캔우리가 생성한 빈이 많으면 많을 수록 @ContextConfiguration에 다 등록해주기도 부담이다. 우선 인터페이스와 구현체 어디다 쓰는 게 좋은지 모르니 다 붙여놓자.
그리고 빈을 자동으로 스캔해주는 빈을 만들어주자.
해당 패키지에 있는 @Service, @Component, @Configuration, @Bean 요런 어노테이션들이 붙은 빈들은 자동으로 스캔하고 생성해주는 어노테이션이다. 이제 테스트에서 Bean 클래스들을 한땀 한땀 넣어주는 부분을 수정해보자.
Config 파일 하나로 코드가 너무나 쾌적해졌다. 어노테이션은 인터페이스에? 구현체에?
어노테이션을 인터페이스에만 붙이면 구현체 타입으로 DI를 받을 수 없다.
하지만 이번엔 빈을 생성하지 못한다는 에러가 나온다. 그럼 이번엔 구현체에만 @Service 어노테이션을 붙이면 어떻게 될까?
이제 테스트를 고쳐보자.
테스트를 돌리면 또 순환참조 오류로 실패한다. 다른 OrderService 구현체에도 @Service 어노테이션을 붙여주자.
이제 테스트를 돌리면 정상적으로 돌아간다. 인터페이스에 어노테이션 안 붙여도 인터페이스 타입으로 느슨하게 결합해서 DI도 가능하고, 특정 구현체에 기능이 쓰고 싶다면 해당 구현체 타입으로 DI도 가능하고… What is circular reference in Spring boot?Circular dependency in Spring happens when two or more beans require instance of each other through constructor dependency injections. For example: There is a ClassA that requires an instance of ClassB through constructor injection and ClassB requires an instance of class A through constructor injection.
How does Spring overcome circular dependency?A simple way to break the cycle is by telling Spring to initialize one of the beans lazily. So, instead of fully initializing the bean, it will create a proxy to inject it into the other bean. The injected bean will only be fully created when it's first needed.
How do you avoid circular dependencies?To reduce or eliminate circular dependencies, architects must implement loose component coupling and isolate failures. One approach is to use abstraction to break the dependency chain. To do this, you introduce an abstracted service interface that delivers underlying functionality without direct component coupling.
|