당니의 개발자 스토리
중복 등록과 충돌 본문
중복 등록과 충돌
이번에는 중복 등록과 충돌에 대해서 알아보겠습니다.

컴포넌트 스캔에서 같은 빈 이름이 등록되면 어떻게 될까요?
두 가지 상황이 있는데요.
자동 빈 등록, 그러니까 둘 다 컴포넌트 스캔한 빈인데 둘 다 이름이 똑같은 거예요. 이게 한 케이스고, 나머지 하나는 수동으로 빈을 등록한 거랑 자동으로 빈을 등록한 게 있어요. 이 경우는 진짜 많이 생기겠죠.
왜냐면 수동으로 해놨는데, 잘못해서 컴포넌트 스캔으로 등록한 거랑 보니까 둘이 똑같은 거예요. 그러면 꽝 충돌이 나겠죠.

먼저 자동 빈 등록과 자동 빈 등록을 하면, 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되었는데, 또 등록이 돼요. 만약에 컴포넌트 스캔 했는데, 이름이 A가 있고 여기도 A가 있어요.
그러면 ConflictingBeanDefinitionException 예외가 터집니다.
한번 간단하게 돌려보면 되는데요. AutoAppConfig에서 잠깐만 해볼게요.

OrderServiceImpl을 service 라고 이름을 바꿔서 빈으로 등록하고요.

MemberServiceImpl도 service 라고 이름을 바꿔서 빈으로 등록했다고 해봅시다.

그 다음에 cmd + B 눌러서 AutoAppConfigTest를 돌려보겠습니다.

그러면 basicScan()이 돌아가면서 쫙 충돌이 나죠.

BeanDefinition이 잘못됐다고 하고,

ConflictingBeanDefinitionException 예외가 발생합니다.

이미 service라는 빈 이름이 존재한다고 알려줍니다. 굉장히 친절하죠. 스프링은 오류 메세지만 잘 봐도 진짜 많이 해결이 돼요. 테스트가 잘 됐으니 다시 원상복구 해줍니다.
따라서, 자동 빈 등록끼리 Conflict 나는 일은 없겠죠. 스프링이 오류를 발생시키니까 이 경우는 거의 없다고 보면 됩니다.

그런데 수동 빈 등록과 자동 빈 등록이 될 때는 주의하셔야 합니다. 이건 있을 수 있습니다.
먼저, MemoryMemberRepository를 보시면

컴포넌트 등록을 했죠. 이건 자동 빈 등록이니까, 우리는 수동으로 'memoryMemberRepository' 라는 이름을 가진 빈을 등록시켜보는 겁니다.

왜 이름을 memoryMemberRepository로 했을까요?

컴포넌트 스캔으로 빈이 등록될 때는 클래스 명에서 맨 앞글자가 소문자로 바뀌기 때문입니다. 이런 설정을 안해주면 수동 등록일 땐 메서드 명을 따르기 때문에 memberRepository로 등록됐을 겁니다.
이제 AutoAppConfigTest에 있는

basicScan()를 돌려보면,

성공했습니다. 같은 이름의 빈이 2개인데 어떻게 성공하지??
여기에는 비밀이 있습니다.

이걸 보시면, Overriding bean definition이 보이시죠.

이러한 경우에는 수동 빈 등록이 우선권을 가집니다. 그게 맞겠죠. 아무래도 자동보다는 수동이죠.
그래서 수동 빈이 자동 빈을 오버라이딩 해줍니다.
스프링이 친절하게 메시지도 남겨줘요. 그러면서 뭘로 replacing 했는지도 알려줍니다.

그런데 물론 개발자 의도적으로 이런 결과를 기대했어요. 그러면 자동보다는 수동이 우선권을 가지는 것이 좋죠. 하지만 현실은 개발자가 의도적으로 설정해서 이런 결과가 만들어지기 보다는 여러 설정들이 꼬여서 이런 결과가 만들어지는 경우가 대부분이에요. 원치않게 오버라이딩이 돼버리는 거예요.
그래서 잘못하면 진짜 잡기 어려운 버그가 만들어져요. 나도 모르는 상황이 발생할 수도 있는 거죠. 그러니까 애초에 애매한 상황을 만들면 안돼요.
그래서 최근 스프링 부트는 어떤 결정을 내렸냐면, 수동 빈 등록과 자동 빈 등록이 충돌이 나면, 그냥 오류가 나면서 튕겨버리도록 기본값을 바꿔버렸어요.

그래서 위 같은 에러가 나요. 스프링 부트인 CoreApplication 을 실행해보면 오류를 볼 수 있는데요.
부트를 통해서 실행해야 돼요.

그래서 이걸 돌려보면,

지금 스프링 부트가 바로 오류를 내버렸어요. 오류 메세지도 친절합니다.

지금 memoryMemberRepository가 AutoAppConfig에 등록이 되어있는데,

여기에도 등록이 되어있대요. 그래서 현재 Overriding이 disable 되어 있대요.
'뭐지?' 하실 수도 있는데요,

스프링 부트는 이러한 overriding 옵션이 있거든요. 이거를 스프링 부트는 기본으로 false로 해놔요.
반면, 스프링은 기본적으로 오버라이딩 하도록 되어있어요. 스프링 프레임워크 자체의 코어 모듈이 그렇게 되어 있는데, 스프링 부트가 최근에 기본적으로 나는 얘를 false로 할 거라고 결정을 했어요.
그래서 기본값이 false로 되어있고, 만약에 오버라이딩 하고 싶으면, application.properties에

이걸 적어줍니다. 이 내용은 스프링 부트가 다 알려줍니다.
이렇게 돌리면,

오류가 안 나고, 수동 빈이 자동 빈을 오버라이딩 되는 거에요.
그래서 다시 false로 하면,

오류가 뜹니다. 스프링 부트가 기본값이 false인 거죠.
그래서 부트를 쓰시면, 오류가 납니다.
왜 부트가 이런 결정을 했겠어요? 실무에서 이런 문제가 생기면 버그를 잡기가 너무 어려운 거예요.
개발 할 땐 명확하지 않은 건 절대 하면 안돼요. 내가 "난 스프링을 너무 잘 알아서 괜찮아. 수동 등록이 우선순위를 갖는 것도 알아!" 그렇다고 해도, 개발은 혼자 하는 게 아니잖아요. 여러 명의 개발자가 같이 하는 거기 때문에 애매한 상황을 만들지 않는 게 가장 중요합니다.
아무리 코드가 줄어들고 예쁘게 보인다고 해도, 어설픈 추상화 보다 코드가 늘어난다고 해도 그게 더 나은 경우가 많습니다. 명확하게 하거나, 아니면 빨리 오류로 튕기거나 하는 게 더 나은 것 같아요.
이렇게 해서 충돌까지도 알아봤습니다.
다음 시간은 의존관계 자동 주입, @Autowired와 관련된 내용을 깊이있게 다루겠습니다.
'스프링 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
| 옵션 처리 (0) | 2024.01.24 |
|---|---|
| 다양한 의존관계 주입 방법 (0) | 2024.01.24 |
| 필터 (0) | 2024.01.24 |
| 탐색 위치와 기본 스캔 대상 (0) | 2024.01.24 |
| 컴포넌트 스캔과 의존관계 자동 주입 시작하기 (0) | 2024.01.24 |