QueryDsl Config 와 WebMvcAutoConfiguration 순환 참조 에러 해결

유콩·2024년 4월 30일
0
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 관련 빈 생성 안됨
    • @DataJpaTest 는 EntityManager 와 기본 엔티티 등만 빈으로 생성
    • QueryDsl 관련 빈 생성 설정
      package gohigher.querydsl;
      
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      import com.querydsl.jpa.impl.JPAQueryFactory;
      
      import jakarta.persistence.EntityManager;
      import jakarta.persistence.PersistenceContext;
      
      @Configuration
      public class JpaQueryDslConfig {
      
          @PersistenceContext
          private EntityManager entityManager;
      
          @Bean
          public JPAQueryFactory jpaQueryFactory() {
              return new JPAQueryFactory(entityManager);
          }
      }
  • 단위 테스트의 정상 동작을 확인하고 인수 테스트를 실행하니 위의 순환 참조 에러 발생
    • 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 한 객체만 직접 호출하여 바인딩
  • 생성자 제거 후 필드 주입으로 변경하여 해결

0개의 댓글