일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 해시
- 구조체배열
- spring boot
- python
- Spring
- C++
- TypeORM
- 카카오
- 코딩테스트
- 컴포넌트스캔
- @Autowired
- 카카오 코테
- OpenCV
- 코테
- 스프링
- Nodejs
- 프로그래머스
- @Component
- nestjs typeorm
- AWS
- 파이썬
- git
- 시스템호출
- thymeleaf
- 가상면접사례로배우는대규모시스템설계기초
- 카카오 알고리즘
- nestJS
- nestjs auth
- 알고리즘
- C언어
- Today
- Total
공부 기록장 💻
[Spring] AOP 적용 시 발생하는 순환 참조 문제 해결하기 (The dependencies of some of the beans in the application context form a cycle) 본문
[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
https://ch4njun.tistory.com/269
위의 두번째 블로그에서는 @Lazy 어노테이션을 통해 임의로 해결할 수 있다는 방안을 잠깐 언급하기도 하지만(권장하는 방법은 아님), 위 블로그에서 예시로 들었던 순환 참조 문제는 A클래스가 B클래스를 의존하고, B클래스가 A클래스를 의존하는 상황이라, 서로 다른 클래스의 각 객체의 의존성 순환 참조 문제가 발생한 것이다. 이는 TimeTraceAop의 의존성 순환 참조와는 조금 다르다고 생각을 하게 되었다.
그래서 오랜 삽질 끝에, 돌아 돌아, 결국 인프런 수강 게시판에서 해답을 찾을 수 있었다.
위의 순환 참조 문제를 제대로 꼬집어 질문을 한 분이 있었다. 그리고 영한 님이 정확하게 답변을 해주셨다.. (이걸 이제야 찾다니)
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..*(..))")