23.03.23 12:57 작성
·
673
·
수정됨
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를 통해 자동 주입할 때는 타입으로 빈을 찾기 때문입니다.
해당 내용에 대한 부연설명과 해결방안은 스프링 핵심 원리 기본편을 수강하시면 더 자세하고 확실하게 아실 수 있습니다 :)
도움이 되셨으면 좋겠습니다!
2023. 03. 23. 15:48
답변감사합니다.
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 {
}