***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| securityConfig defined in file [..\java\main\com\platinouss\bookrecommend\config\SecurityConfig.class]
↑ ↓
| authenticationProviderService defined in file [..\java\main\com\platinouss\bookrecommend\service\AuthenticationProviderService.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.
Process finished with exit code 1
Semi-Project를 진행 중에 위와 같은 에러 메시지를 발견하였다.
해당 메시지는 Spring Boot 2.6.x 버전부터, 순환 참조를 default로 금지함으로서 발생되는 에러메시지이다.
Circular References Prohibited by Default
Circular references between beans are now prohibited by default. If your application fails to start due to a
BeanCurrentlyInCreationException
you are strongly encouraged to update your configuration to break the dependency cycle. If you are unable to do so, circular references can be allowed again by settingspring.main.allow-circular-references
totrue
, or using the new setter methods onSpringApplication
andSpringApplicationBuilder
This will restore 2.5’s behaviour and automatically attempt to break the dependency cycle.
이번 기회에 구현하고 있는 프로젝트에서 순환 참조가 왜 발생했으며, 해결 방법에 대해 기술해보고자 한다.
CBLIB(바이트 코드 조작 라이브러리)를 이용하여, @Configuration이 붙은 클래스를 상속한 임의의 클래스를 만들고 그 임의의 클래스를 빈으로 등록한다. 그렇게 빈으로 등록된 클래스 내부의 @Bean 메서드들은 Spring Container에 존재한다면 Spring Container에 존재하는 빈을 반환하고, 없다면 새로 생성하여 빈으로 등록 후 반환함으로서 싱글톤을 보장받는다.
실제로 @Configuration에 의해 Bean으로 등록된 securityConfig는 다음과 같이 SecurityConfig를 상속받은 프록시 객체가 등록된다. com.platinouss.bookrecommend.config.SecurityConfig$$EnhancerBySpringCGLIB$$895b06d2
Spring에서 친절하게 순환 참조가 일어나는 클래스를 알려준다.
먼저 문제가 되는 클래스는 SecurityConfig
와 AuthenticationProviderService
이다.
해당 클래스들이 어떻게 구현되어있는지 확인해보자.
/**
* SecurityConfig.java
*/
@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationProviderService authenticationProvider;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
// 생략된 코드
}
/**
* AuthenticationProviderService.java
*/
@Service
@RequiredArgsConstructor
public class AuthenticationProviderService implements AuthenticationProvider {
private final JpaUserDetailsService userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
// 생략된 코드
}
AuthenticationProviderService -> SecurityConfig -> AuthenticationProviderService -> ...
해당 코드에서 SecurityConfig
를 Bean으로 등록하려면 authenticationProviderService
Bean을 필요로 하고, AuthenticationProviderService
를 Bean으로 등록하려면 사전에 securityConfig
Bean이 생성되어 있어야 bCryptPasswordEncoder
Bean이 생성될 수 있으므로 SecurityConfig
클래스와 authenticationProviderService
클래스가 서로 순환 참조를 하게되는 구조이다.
결국 어떠한 Bean도 생성하지 못하는 문제가 발생된다.
@Lazy 활용과 같은 일시적인 방법 등이 있지만, 설계 원칙에 따라 적합한 방식은 순환 참조 고리를 끊도록 재설계 하면 된다.
해당 문제의 순환 참조 고리를 끊는 가장 적합한 방법은 bCryptPasswordEncoder를 새로운 @Configuration 클래스의 Bean으로 등록하는 것이다. 그렇게 되면 다음과 같은 구조가 만들어지고, 자연스레 순환 참조의 고리가 끊어지게 된다.
/**
* SecurityConfig.java
*/
@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationProviderService authenticationProvider;
// 생략된 코드
}
/**
* appConfig.java
*/
@Configuration
public class AppConfig implements AuthenticationProvider {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
SecurityConfig -> AuthenticationProviderService -> AppConfig