작성
·
677
·
수정됨
1
안녕하세요.
제가 aop를 프로젝트 다 만들고나서 서버를 구동하니 에러가 좀나더라구요.
우선 상황은
MemoryMemberRepository 클래스파일에 @Repository 어노테이션이 추가되어 있는 상태였으며, TimeTraceAop 클래스 생성 후 강의보면서 강사님의 코드를 입력해서 추가, 이후 서버를 구동하니 에러가 나더라구요.
아래는 에러메세지 입니다.
Parameter 0 of constructor in hello.hellospring.SpringConfig required a single bean, but 2 were found:
- memoryMemberRepository: defined in file [(생략)\hello\hellospring\repository\MemoryMemberRepository.class]
- springDataJpaMemberRepository: defined in hello.hellospring.repository.SpringDataJpaMemberRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration
해당 에러문을 보고 MemoryMemberRepository클래서파일에 추가되어있는 @Repository 어노테이션을 제거 후 서버를 재구동하니 에러없이 정상적으로 프로젝트가 잘 돌아갔습니다.
제 SpringConfig파일에는
@Configuration
public class SpringConfig {
/** 2. 스프링 데이터 JPA 사용후 추가한 부분*/
private final MemberRepository memberRepository;
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
}
이렇게만 코딩되어 있는데요.
여기서 궁금한게 왜 SpringConfig생성자가 호출될때 매개변수로 2개가 들어간건지가 궁금합니다.
원래는 SpringDataJpaMemberRepository 하나만 들어가야 하는거 같은데 어떻게 SpringDataJpaMemberRepository와 MemoryMemberRepository가 동시에 생성자의 매개변수로 들어가려 했는지.. @Repository가 어떤 영향을 주는걸까요?
이전 jpa 강의를 들을때는 MemberRepository로 사용할 매개변수를 직접설정했는데 그 코드도 주석처리가 되어있습니다. 그래서 이해가 되지 않아 게시판이 질문남깁니다
답변 1
1
안녕하세요, 키썬 님. 공식 서포터즈 y2gcoder 입니다.
문제만 보면 MemberRepository의 구현체가 MemoryMemberRepository, SpringDataJpaMemberRepository 가 있고 둘 다 스프링 빈으로 등록되는 상황입니다. MemoryMemberRepository 에 @Repository, SpringDataJpaMemberRepository는 JpaRepository 인터페이스를 상속하고 있습니다.
먼저 @Repository가 붙은 클래스는 애플리케이션 실행 시 컴포넌트 스캔에 의해 스프링 빈으로 등록됩니다 그리고 JpaRepository를 상속한 인터페이스는 애플리케이션 실행 시 스프링에서 자동으로 구현체를 만들어 스프링 빈으로 등록합니다. 그렇게 되면 2개 다 스프링 빈으로 등록된 상태고 이 상태에서 SpringConfig에서 MemberService에 의존성 주입해줄 빈을 MemberRepository 인터페이스 타입으로 찾게 됩니다. 그러면 2개의 빈이 나오고 말씀하셨던 오류가 발생하게 됩니다 :)
해당 부분은 MVC 1편 강의에 자세하게 소개되어있으니 수강해보시는 것을 추천드립니다!
감사합니다.
저도 확실히 답변 드리고 싶어서 간단하게 나마 코드를 짜봤습니다.
폴더 구조는 다음과 같습니다.
TestService -> MemberService
생성자 주입 때 어떤 객체가 주입되는지 보고 싶어서 로깅을 해뒀습니다.
TestRepository -> MemberRepository
TestRepositoryA -> MemoryMemberRepository 등에 대응
TestRepositoryB -> SpringDataJpaMemberRepository 에 대응
빈으로 사용할 각 클래스들의 코드들의 기본 상태는 위와 같습니다.
결론부터 말씀드리면 빈이 각각 등록된 것이 맞고, @Autowired를 사용하는 의존관계 자동 주입을 사용할 때 타입으로 주입할 빈을 찾기 때문입니다. 기존의 케이스에서는 의존관계를 수동으로 직접 주입해주고 있기 때문에 이상없이 작동합니다.
1) TestService: 수동 빈 등록, TestRepositoryA: 수동 빈 등록, TestRepositoryB: 자동 빈 등록
프로젝트 실행 로그:
TestRepositoryA가 testRepository 라는 빈 이름으로 등록되고, TestRepositoryB는 testRepositoryB라는 빈 이름으로 등록된 것을 볼 수 있습니다. 이는 빈 등록 방법의 차이 때문입니다.
- @Bean으로 수동 등록할 때: 메서드 이름이 빈으로 등록됩니다.
- @Component로 자동 등록할 때: 클래스의 첫 글자만 소문자로 바꿔 빈 이름으로 등록합니다.
의존성을 수동으로 직접 주입하고 있기 때문에 문제가 생기지 않습니다.
2) TestService: 자동 빈 등록, TestRepositoryA: 자동 빈 등록, TestRepositoryB: 자동 빈 등록
이 경우는 키썬님께서 처음 질문에서 작성하셨던 것과 똑같은 케이스라고 보시면 될 것 같습니다. 왜냐하면 SpringConfig에서 memberRepository 빈을 의존성 자동 주입해오는 것처럼TestService를 자동 빈 등록하면 관련 의존성을 자동으로 주입해오기 때문입니다.
TestService
위에다가 컴포넌트 스캔의 대상이 될 수 있도록 @Service를 달아주었습니다.
TestRepositoryA
마찬가지로 컴포넌트 스캔의 대상이 될 수 있도록 @Repository를 달아주었습니다.
SpringConfig
모두 주석 처리했습니다.
프로젝트 실행 로그:
컴포넌트 스캔을 통해 스프링 빈을 등록하던 도중 MemberService의 의존성 자동 주입 단계에서 키썬님의 첫번째 코드와 동일한 오류(NoUniqueBeanDefinitionException)를 뱉습니다. 말씀드렸던 것처럼 @Autowired를 통해 자동 주입할 때는 타입으로 빈을 찾기 때문입니다.
해당 내용에 대한 부연설명과 해결방안은 스프링 핵심 원리 기본편을 수강하시면 더 자세하고 확실하게 아실 수 있습니다 :)
도움이 되셨으면 좋겠습니다!
답변감사합니다.
MemoryMemberRepository는 MemberRepository을 상속받고 있고, JPARepository를 상속한 인터페이스도 마찬가지로 MemberRepository를 상속 받고 있기 때문에 둘다 bean으로 등록되면 memberservice가 생성될때 누굴 생성자에 인자로 넣어야 하는지 2개라서 문제가 발생된 문제이군요
그럼 여기서 또 궁금해지는게 있는데..
추가로 질문 드려요!
SpringConfig파일에서 다형성을 이용할때 memberRepository를 어떤걸로 생성할지 설정하는 코드에서 return new MemoryRepository();를 주석한 후
나머지 코드들을 한번씩 실행했을때에도 서버올려서 프로젝트가 잘 진행되었었는데요 이때도 마찬가지로 MemoryMemberRepository클래스에는 @repository 어노테이션이 존재하고 있었는데 그렇다면 이상황도 문제가 되어야 하는게 맞는거 같은데 .. 프로젝트가 잘 진행되었었거든요. 이 경우 둘다 빈으로 등록이 될거같은데 아닌걸까요?
아래는 제가 말씀드린 상황의 코드입니다.
@Configuration
public class SpringConfig {
@Bean
public MemberRepository memberRepository() {
//return new MemoryMemberRepository(); 1. 자바코드
//return new JdbcMemberRepository(dataSource); 2. 순수JDBC연결 사용
//return new JdbcTemplateMemberRepository(dataSource); 3. 스프링 jdbcTEmplate 사용
return new JpaMemberRepository(em); //4. jpa 사용
}
*/
}
@Repository
public class MemoryMemberRepository implements MemberRepository {
}