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