The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| authConfig defined in URL [jar:file:/Users/yukong/repository/side-project/go-higher/in-adapter-api/build/libs/in-adapter-api-0.0.1-SNAPSHOT-plain.jar!/gohigher/config/AuthConfig.class]
↑ ↓
| querydslPredicateArgumentResolver defined in class path resource [org/springframework/data/web/config/QuerydslWebConfiguration.class]
↑ ↓
| org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration
└─────┘
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.
문제 배경
- 복잡한 쿼리문을 구현하고자 QueryDsl 적용
- 단위 테스트를 위해
@DataJpaTest
를 적용하니 QueryDsl 관련 빈 생성 안됨
- 단위 테스트의 정상 동작을 확인하고 인수 테스트를 실행하니 위의 순환 참조 에러 발생
- AuthConfig.class 에서
HandlerMethodArgumentResolver
를 참조하여 QueryDsl 관련 Resolver 인 QuerydslPredicateArgumentResolver
를 필요로 함
QuerydslWebConfiguration
에서 QuerydslPredicateArgumentResolver
빈 생성
- 정확한 위치는 찾지 못했으나
QuerydslPredicateArgumentResolver
을 생성하기 위해 WebMvcAutoConfiguration
을 참조한 것으로 추측
WebMvcAutoConfiguration
에서는 AuthConfig.class 가 구현한 WebMvcConfigurer
를 참조했기 때문에 결국 순환 참조가 발생했다 추측
- AuthConfig → QuerydslPredicateArgumentResolver → WebMvcAutoConfiguration → …
문제 분석
- QueryDsl 을 적용하기 전에는 WebMvcAutoConfiguration → AuthConfig 의 방향으로 단방향
- AuthConfig 는 CORS 를 위해 직접 구현한 설정 빈
- QueryDsl 테스트를 위해서 현재로써는 QuerydslPredicateArgumentResolver 를 생성하는 것이 불가피
- WebMvcAutoConfiguration 은 Spring 의 기본 빈
- 즉, 직접 개입할 수 있는 객체는 AutoConfig 가 유일
해결
변경 전 코드
package gohigher.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AuthConfig implements WebMvcConfigurer {
private static final String ALLOWED_METHOD_NAMES = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH";
private static final String SPLIT_REGEX = ",";
private final List<HandlerMethodArgumentResolver> resolvers;
private final String allowedOrigin;
public AuthConfig(List<HandlerMethodArgumentResolver> resolvers,
@Value("${cors-config.allowed-origin}") String allowedOrigin) {
this.resolvers = resolvers;
this.allowedOrigin = allowedOrigin;
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(allowedOrigin)
.allowedMethods(ALLOWED_METHOD_NAMES.split(SPLIT_REGEX))
.allowCredentials(true)
.exposedHeaders(HttpHeaders.LOCATION);
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.addAll(this.resolvers);
}
}
- 방법1) WebMvcConfigurer 를 참조하여 순환 참조가 발생했으므로 WebMvcConfigurer 를 구현하는 것이 아닌 다른 방법으로 CORS 문제 해결
- 방법2) HandlerMethodArgumentResolver 를 호출하면서 QuerydslPredicateArgumentResolver 가 참조되었으므로 해당 과정을 생략
- Custom 한 Resolver 가 있어 안됨 (Token → UserId 추출)
변경 후 코드
package gohigher.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import gohigher.support.auth.LoginArgumentResolver;
@Configuration
public class AuthConfig implements WebMvcConfigurer {
private static final String ALLOWED_METHOD_NAMES = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH";
private static final String SPLIT_REGEX = ",";
@Autowired
private LoginArgumentResolver loginArgumentResolver;
@Value("${cors-config.allowed-origin}")
private String allowedOrigin;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(allowedOrigin)
.allowedMethods(ALLOWED_METHOD_NAMES.split(SPLIT_REGEX))
.allowCredentials(true)
.exposedHeaders(HttpHeaders.LOCATION);
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginArgumentResolver);
}
}
- 변경 전 AuthConfig.class 의 생성자를 호출할 때 파라미터의 List 에서 순환 참조 에러가 발생했었음
- 해당 부분을 제거하니 직접 생성한 Custom Resolver 인 LoginArgumentResolver 가 등록되지 않아 동작하지 않음
- 실제로 Custom 한 객체만 직접 호출하여 바인딩
- 생성자 제거 후 필드 주입으로 변경하여 해결