관리 메뉴

공부 기록장 💻

[Spring] AOP 적용 시 발생하는 순환 참조 문제 해결하기 (The dependencies of some of the beans in the application context form a cycle) 본문

# Develop/삽질하며 배우기 🔨

[Spring] AOP 적용 시 발생하는 순환 참조 문제 해결하기 (The dependencies of some of the beans in the application context form a cycle)

dream_for 2023. 1. 18. 13:35

 

오류 발생 상황

AOP에 대해 학습하는 과정에서,

시간 측정 로직을 담은 TimeTraceAop가 Configuration 내에 등록된 Bean인 상황에서,

해당 클래스를 관심 사항으로 따로 분리하여 @Aspect를 적용할 때 순환 참조 오류가 발생하였다. 

 

 

우선 @Aspect를 적용한 AOP는 다음과 같다.

@Around 애노테이션 부분을 보면, hellospring 패키지 내에 있는 모든 메서드에 대해 해당 AOP를 수행하겠다는 것을 나타낸 것이다. @Component를 적용해 컴포넌트 스캔 대상임을 명시하였다.

 

@Aspect
@Component
public class TimeTraceAop {

  @Around("execution(* hello.hellospring..*(..))")
  
  public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    System.out.println("START: " + joinPoint.toString());
    try {
      return joinPoint.proceed();
    } finally {
      long finish = System.currentTimeMillis();
      long timeMs = finish - start;
      System.out.println("END: " + joinPoint + " " + timeMs + "ms");
    }
  }
}

 

이후 Configuration 설정 파일에서 timeTraceAop 객체에 대해 @Bean 을 적용해 스프링 빈으로 등록을 하였다.

 

@Configuration
public class SpringConfig {

  private final MemberRepository memberRepository;

  public SpringConfig(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
  }

  @Bean
  public MemberService memberService() {
    return new MemberService(memberRepository);
  }

  @Bean
  public TimeTraceAop timeTraceAop() {
    return new TimeTraceAop();
  }
}

 

오류 발생 원인

간단하게 설명하자면 에러가 발생한 일차적인 이유는 @Bean, @Component 모두 적용되어 스프링 빈이 중복 등록되어 충돌이 일어난 것이다. 하지만 본질적인 문제는 AOP 대상으로 설정된 TimeTraceAop가 자기 자신을 호출함에 따라 스프링 빈의 순환 참조가 발생한 것이라고 할 수 있다. 

아래는 해결 과정으로, 삽질을 하며 힘겹게 원인을 찾아나갔던 과정이 아까워서 고민했던는 과정을 생략하지 않고 놔두기로 하였다.

 

 

 

처음 발생한 오류 문장은 다음과 같았다.

 

The bean 'timeTraceAop', defined in class path resource [경로], could not be registered. A bean with that name has already been defined in file [TimeTraceAop.class 경로] and overriding is disabled.

 

위 메시지는 timeTraceAop 이름의 빈이 등록되지 못한다, 이미 같은 이름의 빈이 등록되어 있으며 오버라이딩은 불가능하다고 설명하고 있다. 

이는 위에서 언급한 일차적인 원인으로, @Component 와 @Bean 이 모두 TimeTraceAop에 대해 적용되어 빈으로 중복 등록된 것이 문제이지 않을까? 하는 추측을 하게 된 계기이다.

 

제시된 해결 방안

해결 방법으로는 다음과 같이 spring.main.allow-bean-definition-overriding=true 옵션을 지정하라는 방법을 제시하였다.

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

 

 

 

 

첫번째 시도

따라서 위의 해결 방안을 그대로 시도해보았다.

 

1. application.properties에  spring.main.allow-bean-definition-overriding=true 속성 추가

 

 

 

두번째 오류 발견 - 순환 참조 문제 발생

위 방법을 시도했지만, 그럼에도 오류가 발생하였다. 이번에는 다른 에러 메시지가 나타났다.

 

 

애플리케이션 내 스프링 빈들의 의존성이 순환을 만들고 있다는 것이다.

즉, 스프링 빈 등록의 오버라이딩을 허용했음에도, 의존성의 순환 문제가 발생한 것이다. Application Context, 즉 스프링 컨테이너에서 어떤 빈들의 의존 관계가 순환 문제를 발생시키고 있다.

그리고 그 아래 timeTraceAop 가 순환 문제를 일으키고 있다고 명시하고 있다.

 

The dependencies of some of the beans in the application context form a cycle:

┌──->──┐
|  timeTraceAop defined in class path resource [hello/hellospring/SpringConfig.class]
└──<-──┘

 

 

스프링 빈의 순환이 무엇일까..?

 

아래 블로그 글을 보고 스프링 빈의 순환 종속성(Circular Dependencies) 가 무엇인지 이해할 수 있었다.

 

https://hungrydiver.co.kr/bbs/detail/develop?id=90

 

스프링 빈의 순환 종속성 문제 (Circular Dependencies in Spring)

 

hungrydiver.co.kr

 

https://ch4njun.tistory.com/269

 

[Spring Boot] 순환참조문제

순환참조문제란? 위 예외는 애플리케이션 로딩 과정에서 순환참조가 발생함을 알리는 예외이다. 순환참조 문제란 A 클래스가 B 클래스의 Bean 을 주입받고, B 클래스가 A 클래스의 Bean 을 주입받는

ch4njun.tistory.com

 

위의 두번째 블로그에서는 @Lazy 어노테이션을 통해 임의로 해결할 수 있다는 방안을 잠깐 언급하기도 하지만(권장하는 방법은 아님), 위 블로그에서 예시로 들었던 순환 참조 문제는 A클래스가 B클래스를 의존하고, B클래스가 A클래스를 의존하는 상황이라, 서로 다른 클래스의 각 객체의 의존성 순환 참조 문제가 발생한 것이다. 이는 TimeTraceAop의 의존성 순환 참조와는 조금 다르다고 생각을 하게 되었다.

 

 

그래서 오랜 삽질 끝에, 돌아 돌아, 결국 인프런 수강 게시판에서 해답을 찾을 수 있었다.

위의 순환 참조 문제를 제대로 꼬집어 질문을 한 분이 있었다. 그리고 영한 님이 정확하게 답변을 해주셨다.. (이걸 이제야 찾다니)

 

https://www.inflearn.com/questions/48156/aop-timetraceaop-%EB%A5%BC-component-%EB%A1%9C-%EC%84%A0%EC%96%B8-vs-springconfig%EC%97%90-bean%EC%9C%BC%EB%A1%9C-%EB%93%B1%EB%A1%9D

 

AOP(TimeTraceAop)를 @Component 로 선언 vs SpringConfig에 @Bean으로 등록 - 인프런 | 질문 & 답변

안녕하세요. 김영한 팀장님,  AOP(TimeTraceAop)를 @Component로 선언하지 않고 SpringConfig에 @Bean으로 등록할 수 있다고 설명하셨는데 실제로 코드를 돌려보면 빈 순환 참조 에러가 발생합니다. 강의대로

www.inflearn.com

 

TimeTraceAop 의 AOP 대상을 지정하는 @Around 코드를 보면, hello.hellospring 패키지 내 모든 메서드가 AOP의 대상이라는 점에서, SpringConfig의 timeTraceAop() 메서드도 AOP 대상에 포함이 된다는 것이 문제를 야기한다. 이 코드가 자기 자신인 TimeTraceAop를 생성하는 코드이므로, 순환 참조 문제가 발생하는 것이다.

 

  @Around("execution(* hello.hellospring..*(..))")

 

 

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.

 

해결 방안으로는 위와 같이 spring.main.allow-circular-references 값을 true로 지정하라는 메시지를 전달해주었다. 하지만 이런 옵션을 통해 순환 문제를 끊기보다는, 위에서 중복으로 빈이 등록되는 것을 방지하는 방법을 사용해보고자 했다.

 

 

2. Configuration에서 @Bean 제거

 

결국, Configuration에서 작성한 TimeTraceAop의 @Bean을 제거함으로써 해결할 수 있었다.

이를 통해 기존에 설정 정보 파일에서 수동적으로 스프링 컨테이너에 빈으로 등록한 것을 생략한 것이다.

 

 

이제 문제가 발생했던 테스트 코드가 아래와 같이 정상적으로 실행되는 것을 확인할 수 있다.

 

 

 

또다른 해결 방안, @Around에서 AOP 적용 대상에서 SpringConfig를 제외시키기

또다른 해결 방법으로는, @Bean 으로 등록한 TimeTraceAop 메서드를 포함한 SpringConfig 를 제외하는 것이다.

그러면 자기 자신을 생성하는 코드는 AOP 대상에서 제외되어, 더이상 순환 참조가 일어나지 않는다.

 

    @Around("execution(* hello.hellospring..*(..)) && !target(hello.hellospring.SpringConfig)")
    //@Around("execution(* hello.hellospring..*(..))")

 

728x90
반응형
Comments