인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

창신동 장첸님의 프로필 이미지
창신동 장첸

작성한 질문수

스프링 핵심 원리 - 기본편

중복 등록과 충돌

충돌문제 강의실습과 다른 결과가 나타납니다.

작성

·

140

·

수정됨

0

06:03 부터 @SpringBootApplication어노테이션으로 스프링구동을 진행함으로서

앞서 AutoAppConfigTest 를 했을 때 replace되어 정상구동되는 현상과 반대되는 결과를 보여주시고 계십니다.

 

스프링부트는 오류를 발생시키는 것을 저도 똑같이 확인하기 위해 실습을 했는데요. 결과메세지가 달랐습니다.

 

Parameter 0 of constructor in hello.core.member.MemberServiceImpl required a single bean, but 2 were found:
	- memoryMemberRepository: defined in file [C:\Users\eh\Desktop\Source\core\out\production\classes\hello\core\member\MemoryMemberRepository.class]
	- memberRepository: defined by method 'memberRepository' in class path resource [hello/core/AppConfig.class]

강의에서는 AutoAppConfig에 작성한 수동빈과 @Compoent로 등록된 자동빈의 충돌을 다뤘으나, 저의 경우 AppConfig가 등장했습니다.

 

강사님도 AppConfig를 건들지 않으신것으로 알고있습니다. 다음과 같습니다.

@Configuration
public class AppConfig {

    public AppConfig() {
        System.out.println("AppConfig 생성자호출");
    }

    // 서비스단
    @Bean
    public MemberService memberService(){
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService(){
        System.out.println("call AppConfig.orderService");
        return new OrderServiceImpl(
                memberRepository(),
                discountPolicy());
    }

    // Repository
    @Bean
    public MemberRepository memberRepository(){
        System.out.println("call AppConfig.memberRepository");
        return new MemoryMemberRepository();
    }

    @Bean
    public DiscountPolicy discountPolicy(){
        System.out.println("call AppConfig.discountPolicy");
        return new RateDiscountPolicy();//return new FixDiscountPolicy();

    }
}

 

다음은 자동구성입니다.

@ComponentScan(
    excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
    public AutoAppConfig() {
        System.out.println("AutoAppConfig의 생성자호출");
    }

    // [수동빈등록]
    // 자동등록 된 빈과 충돌이 발생하지 않으며 아래 수동등록 빈으로 덮어써짐
    @Bean(name = "memoryMemberRepository")
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}
@Component
public class MemoryMemberRepository implements MemberRepository{...}

 

총 3개의 코드가 충돌을 일으킬 후보를 추려봤습니다.

  1. AppConfig의 MemberRepository 수동등록 코드(name = "memberRepository")

  2. AutoAppConfig의 MemberRepository 수동등록 코드(name = "memoryMemberRepository")

  3. MemoryMemberRepository 자동등록 코드(name = "memoryMemberRepository")

강사님은 2,3이 충돌했는데

저는 왜 1,3이 충돌한 걸까요ㅠㅠ

답변 2

1

안녕하세요. 창신동 장첸님, 공식 서포터즈 y2gcoder입니다.

혹시

AutoAppConfig@Configuration 을 불여주고 테스트하셨을까요?

적어주신 AutoAppConfig 에서는 @ComponentScan 애노테이션만 붙어 있어 질문드리게 되었습니다!

 

감사합니다.

아이고....

강의자료에 @Configuration이 윗장에 짤려서 밑에부분만 복붙됐었네요.

정말 감사드립니다. 스프링부트3버전 이슈와 관계없이 Build and Run을 intellij 로 설정해도

강사님꼐서 진행한 결과 그대로 확인할 수 있게됐습니다.

 

정리하자면, @Configuration이 없었기 때문에

AutoConfig 빈객체가 만들어지지 않았고 그 안에 정의된 @ComponentScan과 수동빈 정의들이 아무쓸모 없어졌던 것입니다.

 

그리고 1,3 충돌할 수 있었던 이유는

MemberServiceImpl에 필요한 의존성은 타입을 기반으로 찾기 때문에

1,3의 경우처럼 아무리 빈이름이 다르더라도 ctx.getBean(클래스명) 처럼 클래스와 그 자식클래스가 조회되기 때문에 뭘 골라쓸지 몰라 충돌이 발생했던 것입니다.

 

다시 한번 감사드립니다.

@y2gcoder 님 질문 하나만 더 드리고 싶습니다.

강의 7:00 에 spring.main.allow-bean-definition-overriding=true 을 application.prorperties에 명시를 하면 에러가 발생되지 않음을 보여주셨는데요.

이 속성을 적용하기전

  1. AppConfig의 MemberRepository 수동빈(name = "memberRepository")

  2. AutoAppConfig의 MemberRepository 수동빈(name = "memoryMemberRepository")

  3. MemoryMemberRepository 자동빈(name = "memoryMemberRepository")

속성을 적용하면 다음과 같은 결과가 그려집니다.

  1. AppConfig의 MemberRepository 수동빈(name = "memberRepository")

  2. AutoAppConfig의 MemberRepository 수동빈(name = "memoryMemberRepository") --> 오버라이딩으로 수동이 살아남음

  3. MemoryMemberRepository 자동빈(name = "memoryMemberRepository")

     

    --> 오버라이딩으로 자동빈은 사라짐

위 결과로 인해 제가 진행한 실습은 MemberServiceImpl 생성오류가 발생합니다. 위 1, 2가 MemberServiceImpl의 의존성 후보로서 충돌하기 때문입니다.

강의를 보면 강사님은 1, 2가 충돌하지 않으셨는데요. 어떤 이유인지 알 수 있을까요?

이런 경우 spring.main.allow-bean-definition-overriding=true 설정을 적용하는 이유는, 동일한 이름의 빈이 여러 개 등록될 때 우선순위를 명시적으로 정하기 위해서입니다. 예를 들어, 수동 빈이 자동 빈을 오버라이드하게 하려는 의도라면 이 설정이 필요하지만, 빈 이름이 서로 다르면 빈 충돌은 발생하지 않습니다 🙂

이게 진짜 스프링부트3버전 이슈와 관계있었네요.
Build and Run을 gradle로 설정하니까

강사님 실습대로 나왔습니다.

감사합니다!

0

안녕하세요, 인프런 AI 인턴입니다. 창신동 장첸님께서 스프링 강의를 진행하시면서 발생한 문제에 대해 살펴보겠습니다. 현재 겪고 계신 문제는 “빈 충돌”과 관련된 내용입니다. 여러분께서 주신 코드를 보면, AppConfigAutoAppConfig에서 모두 MemoryMemberRepository라는 동일한 유형의 빈을 등록하고 있어서 발생하는 문제로 보입니다.

스프링에서는 수동으로 등록한 빈이 자동으로 등록된 빈을 덮어쓰는 것이 일반적입니다. 그러나 현재 에러 메시지를 보면 두 개의 빈이 동시에 존재하여, 어떤 빈을 주입해야 할지 스프링이 알 수 없어서 문제가 발생하고 있습니다.

해결 방안:

  1. 빈의 이름을 명시적으로 지정: AutoAppConfig의 수동 등록 빈에 이름을 명시적으로 주는 것도 하나의 방법입니다. 이미 시도하신 것처럼 @Bean(name = "memoryMemberRepository")를 사용하여 이름 충돌을 피할 수 있습니다.

  2. 자동 빈 등록을 제외: ComponentScan을 하실 때, MemoryMemberRepository가 직접 스캔되지 않도록 조정할 수 있습니다. 즉, @Component 대신 다른 등록 방법을 사용하는 것이 좋습니다.

  3. 구성 클래스 정리: 만약 AppConfigAutoAppConfig 둘 중 하나만 사용하도록 코드를 변경할 수 있다면, 중복 빈 등록을 예방할 수 있습니다. 구성 클래스를 한 곳으로 통합하여 모든 빈들이 확실하게 한 곳에서 관리되도록 하는 것도 좋은 방법입니다.

관련된 질문과 비슷한 해결책들을 살펴보시려면 아래 링크를 참고하시기 바랍니다:
- 스프링 빈 중복 등록 해결 방법 : 이 링크에서는 스프링 빈 충돌 문제와 관련한 예시와 해결책을 설명하고 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

창신동 장첸님의 프로필 이미지
창신동 장첸

작성한 질문수

질문하기