[Spring] 순환 참조(Circular References) 해결하기

p1atin0uss·2022년 9월 17일
5

Troubleshooting

목록 보기
1/1
post-custom-banner

💡 개요


***************************
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 setting spring.main.allow-circular-references to true, or using the new setter methods on SpringApplication and SpringApplicationBuilder This will restore 2.5’s behaviour and automatically attempt to break the dependency cycle.

이번 기회에 구현하고 있는 프로젝트에서 순환 참조가 왜 발생했으며, 해결 방법에 대해 기술해보고자 한다.



📘 사전 지식


@Bean

특징

  • @Bean이 붙은 메서드는 싱글톤이므로 여러번 호출되어도 동일한(하나의) 객체만 return된다고 알고있지만, @Configuration 클래스 내부에서 지정된 @Bean 메서드에만 한정되는 이야기이다. @Configuration 메서드 내부에 있지 않은 @Bean 메서드는 싱글톤을 보장받을 수 없다.

싱글톤 보장 원리

  • CBLIB(바이트 코드 조작 라이브러리)를 이용하여, @Configuration이 붙은 클래스를 상속한 임의의 클래스를 만들고 그 임의의 클래스를 빈으로 등록한다. 그렇게 빈으로 등록된 클래스 내부의 @Bean 메서드들은 Spring Container에 존재한다면 Spring Container에 존재하는 빈을 반환하고, 없다면 새로 생성하여 빈으로 등록 후 반환함으로서 싱글톤을 보장받는다.

  • 실제로 @Configuration에 의해 Bean으로 등록된 securityConfig는 다음과 같이 SecurityConfig를 상속받은 프록시 객체가 등록된다. com.platinouss.bookrecommend.config.SecurityConfig$$EnhancerBySpringCGLIB$$895b06d2



📌 문제 분석


Spring에서 친절하게 순환 참조가 일어나는 클래스를 알려준다.
먼저 문제가 되는 클래스는 SecurityConfigAuthenticationProviderService이다.

해당 클래스들이 어떻게 구현되어있는지 확인해보자.

/**
 * 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

profile
지식의 깊이는 곧 이해의 넓이 📚
post-custom-banner

0개의 댓글