본문 바로가기
Error Handling Log

Circular Dependencies error

by GGShin 2022. 7. 25.

언제 만나도 새로운 에러들 ㅎㅎ

 

Circular Dependency에러는 아래와 같이 코드를 구성했을 때 만나게 되었습니다.

비밀번호를 BCryptPasswordEncoder를 사용해 암호화하려는게 목적이었습니다.

 

지금 다시 코드를 보니 왜 AppSecurityConfig에서는 사용하지도 않을 BCryptPasswordEncoder를 DI받으려고 했는지 모르겠지만, 

@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;

그때는 잠시 헷갈렸던 것 같습니다.

 

그래도 덕분에 Circular dependency라는 것은 사용을 권하지 않는다는 것을 알게되었고, 이후에 의존성을 설정할 때 주의해야겠다는 생각도 하게되었습니다.

 

 

 

[AppSecurityConfig]

@Configuration
@EnableWebSecurity //Spring security filter가 Spring Filter Chain에 등록됨
public class AppSecurityConfig {

    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
//...
}

[IndexController]

@NoArgsConstructor
@AllArgsConstructor
@Controller
public class IndexController {

  //...

    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;

 //...

    @PostMapping("/join") //회원가입 완료 후
    @ResponseBody
    public String join(Member member) {
        member.setRole("ROLE_USER");
        String rawPassword = member.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);
        member.setPassword(encPassword);
        memberRepository.save(member);
        return "redirect:/login";
    }

}

 

일단 에러가 나왔을때 에러 로그를 찬찬히 살펴봤습니다.

친절하게 콘솔창에 에러 해결법에 대한 제안이 적혀있었는데,

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.

 마지막에 allow-circular-references를 true로 설정하면 되긴 한다는데, 일단 앞부분에 circular-depency 자체가 사용이 장려되지 않는다고 하니 그렇게 설정을 바꾸지말고 근본적인 해결책을 찾아야겠다는 생각이 들었습니다.

 

그래서 먼저 circular-depency가 구체적으로 무엇인지 찾아보았는데요,

Bean A → Bean B → Bean A와 같이 bean들이 서로 서로 참조하는 경우가 이에 해당한다고 하더라구요.

 

이 문제는 사실 constructor로 주입을 할때만 발생한다고 합니다. 왜냐! constructor를 이용한 주입을 할 때는 context가 로딩될 때 주입이 일어나게 되는데, Bean A → Bean B → Bean A와 같은 의존형태를 띄고 있는 bean들이 있다면 어떤 bean부터 Spring이 생성해야하는지 정할 수가 없기 때문입니다. Constructor가 아닌 setter등의 방식으로 주입을 할 때는 context 로딩 시점이 아닌 필요할 때 주입이 되기 때문에 이런 문제가 일어나지 않는다고 합니다.  

 

Circular dependency가 아닌 Bean A → Bean B → Bean C 관계가 있다고 한다면, 

Spring은 Bean C→ Bean B → Bean A 순서로 생성을 해서 주입을 하게 됩니다. 하지만 Bean A → Bean B → Bean A 같은 형태라면 정말 어떤 것부터 생성해야 하는지 Spring이 헷갈릴만 하겠습니다.

 

제 코드의 경우는 Bean A Bean A를 시도해서 해당 에러가 나온 케이스가 아니었나 싶습니다. AppSecurityConfig.java 에서 말입니다. AppSecurityConfig.java에서 BCryptPasswordEncoder를 Bean으로 등록하고, 같은 class 내에서 해당 Bean 객체를 주입받으려고 하다보니 Spring이 어떻게 해야할지를 몰랐던 것 같습니다. 

@Configuration
@EnableWebSecurity //Spring security filter가 Spring Filter Chain에 등록됨
public class AppSecurityConfig {

	//여기서 주입받으려고 시도
    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;
    
    //여기서 Bean 등록
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    //...
}

비밀번호 암호화해서 DB에 저장하는 방법 연습해보려고 두 번째 작성해보는 코드였는데,

첫번째 작성할 때는 오류가 없었는데 두번째 때 해당 에러를 만나서 의아했었습니다. 

덕분에 이런 에러도 있다는 걸 알게되었으니 다음부터는 주의해야겠습니다!

 

 


참고자료

 

https://www.baeldung.com/circular-dependencies-in-spring

 

Circular Dependencies in Spring | Baeldung

A quick writeup on dealing with circular dependencies in Spring: how they occur and several ways to work around them.

www.baeldung.com

 

반응형