[오늘 만난 오류] 스프링 빈 순환 참조
Updated:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
memberController defined in file [/Users/sanha/SpringStudy/promisor/out/production/classes/promisor/promisor/domain/member/api/MemberController.class]
┌─────┐
| memberService defined in file [/Users/sanha/SpringStudy/promisor/out/production/classes/promisor/promisor/domain/member/service/MemberService.class]
↑ ↓
| webSecurityConfig defined in file [/Users/sanha/SpringStudy/promisor/out/production/classes/promisor/promisor/global/config/WebSecurityConfig.class]
↑ ↓
| jwtProvider defined in file [/Users/sanha/SpringStudy/promisor/out/production/classes/promisor/promisor/global/config/security/JwtProvider.class]
└─────┘
애플리케이션 컨텍스트에서 일부 Bean의 종속성이 순환주기를 형성하는 문제가 발생하였다.
순환 참조 분제는 둘 이상의 Bean이 생성자를 통해 서로를 주입하려고 할 때 발생한다.
memberService (1)
→ webSecurityConfig (2)
→ jwtProvider (3)
어플리케이션이 실행하면 스프링 컨테이너는 3 → 2 → 1 순으로 객체를 생성한다.
하지만 1 → 2 → 3 순으로 객체들이 의존하기 때문에 어떤 객체를 먼저 생성해야 하는지 문제가 발생한다.
스프링은 컨텍스트를 로드 하는 동안 BeanCurrentlyInCreationException
을 발생시킨다.
@Component
@RequiredArgsConstructor
public class JwtProvider {
private final String secretKey = "sd92dfs0-1544-32da-bd21-bd234slkj";
private final Long accessExpireTime = 60 * 60 * 1000L; // 3시간
private final Long refreshExpireTime = ((60 * 60 * 1000L) * 24) * 60; // 60일
private final MemberService memberService;
생성자 주입 방식의 경우, 스프링빈을 등록할 때, 의존관계 주입이 같이 일어난다.
애플리케이션 컨텍스트의 시작 과정에서 모든 싱글톤 빈을 즉시 생성하며 컴파일 과정에서 모든 가능한 오류를 즉시 파악한다.
@Lazy
에너테이션을 사용하여 지연로딩을 통해 임시 방편으로 문제를 해결하였다.
어플리케이션이 실행하는 초기화가 아닌 필요에 따라 lazy-resolution proxy가 주입되는 방식으로 사용한다.
@Component
public class JwtProvider {
private final String secretKey = "sd92dfs0-1544-32da-bd21-bd234slkj";
private final Long accessExpireTime = 60 * 60 * 1000L; // 3시간
private final Long refreshExpireTime = ((60 * 60 * 1000L) * 24) * 60; // 60일
private final MemberService memberService;
public JwtProvider(@Lazy MemberService memberService) {
this.memberService = memberService;
}
하지만 이 방식은 임시 방편에 불과할 뿐이다.
스프링 빈 순환 참조 문제를 해결하는 방법은 여러 가지가 있는데 그중에서 제일 먼저 고려해야 할 해결법은 컴포넌트들을 재설계하는 방법이다.
순환 참조 문제가 발생했다는 것은 보통 설계를 잘못했다는 의미이다. 각 객체들 간의 책임을 명확히 구분해주고 계층관계가 잘 설계되도록 디자인 해야한다.
기존의 설계방식의 문제점은 memberService에 너무 많은 책임을 위임했다는 것이었다.
memberService에 UserDetailsService
를 상속받아서 사용하고 있었는데 이러한 설계의 문제점은 loadUserByUsername 메서드를 사용하기 위해 WebSecurityConfig
객체와 JwtProvider
객체가 memberService에 의존하게 된다는 것이었다.
때문에 다음과 같은 순환 참조가 형성되었다.
해결책
UserDetailsService
를 상속받는 CustomUserDetailService
객체를 별도로 생성해 주었다.
이로인해 WebSecurityConfig
객체와 JwtProvider
객체는 memberService를 의존하지 않고 CustomUserDetailService
에 의존하게 된다.
변경된 코드
@Service
@RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String email) {
if (email.isBlank()) {
throw new EmailEmptyException();
}
Optional<Member> optionalMember = memberRepository.findByEmail(email);
Member member = optionalMember.orElseThrow(LoginInfoNotFoundException::new);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(member.getRole()));
return new User(member.getEmail(), member.getPassword(), authorities);
}
}
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtProvider jwtProvider;
private final WebAccessDeniedHandler webAccessDeniedHandler;
private final CustomAuthenticationEntryPoint authenticationEntryPointHandler;
private final CustomUserDetailService customUserDetailService;
private final PasswordEncoder passwordEncoder;
@Component
@RequiredArgsConstructor
public class JwtProvider {
private final String secretKey = "sd92dfs0-1544-32da-bd21-bd234slkj";
private final Long accessExpireTime = 60 * 60 * 1000L; // 3시간
private final Long refreshExpireTime = ((60 * 60 * 1000L) * 24) * 60; // 60일
private final CustomUserDetailService customUserDetailService;
빈들간의 의존관계 또한 다음과 같이 개선되었다.
참조
https://www.baeldung.com/circular-dependencies-in-spring
Leave a comment