쵸오오오오오오오오잉 입니다
![]()
🚨 문제 상황
- 스프링에서 인터페이스를
@Autowired를 통해 의존성 주입할 때 이걸 구현할 클래스가 2개 이상일 때 어떤 클래스를 주입해야 될지 몰라서 스프링에서는 예외를 발생시킴- Service라는 클래스에서 Dao라는 인터페이스를 DI로 주입받고 있는 상황
- Dao라는 인터페이스는 2개의 클래스가 구현하고 있음(AAADao, BDDDao)

| 잉..? 그냥 구현 클래스를 주입받은면 되는 거 아닌가?
- 안될 건 없음 오히려 하면 위와 같은 문제는 해결됨
- 단, 객체 지향 설계 원칙 SOLID에서 의존 역전 원칙(Dependency Inversion Principle) "구현체에 의존하면 안 되고 추상화에 의존해야 된다"라는 원칙에 어긋남
- 또한 추상화에 의존하면 유연하게 변경이 가능하여 확장성에 좋음(유지보수 용이)
참고 코드
// 인터페이스
public interface FileUploader {
String fileUpload(File file);
}
// 인터페이스 구현체
@Component
public class AwsS3Uploader implements FileUploader{
@Override
public String fileUpload(File file) {
// 구현코드
}
}
// 인터페이스 구현체
@Component
public class LocalFileUploader implements FileUploader{
@Override
public String fileUpload(File file) {
// 구현 코드
}
}
@Service
public class FileService {
private FileUploader fileUploader;
// FileUploader 라는 인터페이스로 주입시 오류 발생 !
public FileService(FileUploader fileUploader) {
this.fileUploader = fileUploader;
}
}💡 해결방법

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
1. @Primary 를 통해 주입받는 우선순위를 부여
@Primary
@Component
public class AwsS3Uploader implements FileUploader {
@Override
public String fileUpload(File file) {
return "";
}
}- 컴포넌트 스캔 대상인 어노테이션 (Component, Service, Controller 등..) 혹은
@Bean에서 @Primary를 통해 우선순위를 부여 가능 - 구현체가 여러 개인 인터페이스를 주입받는 클래스의 경우 @Primary 로 우선순위 부여받은 클래스가 주입됨
2. @Qualifier 를 통해 주입할 클래스를 선택
@Service
public class FileService {
private FileUploader fileUploader;
public FileService(@Qualifier("awsS3Uploader") FileUploader fileUploader) {
this.fileUploader = fileUploader;
}
}- 생성자 주입의 경우
@Qualifier어노테이션에서 네이밍을 통해 주입할 클래스를 선택할 수 있음 이 '네이밍' 은 기본적으로 클래스명에서 첫 글자만 소문자로 한 값이 기본값 @Component어노테이션에 이름을 따로 지정하면 그 이름으로 Qualifier를 지정할 수 있음@Component("aws")로 지정할 경우 생성자 주입에서 아래와 같이 사용이 가능public FileService(@Qualifier("aws") FileUploader fileUploader) { this.fileUploader = awsS3Uploader; } }
@Primary 로 우선순위가 부여되더라도 @Qualifier 가 우선적으로 주입된다 즉 @Qualifer 의 우선순위가 더 높다
3. @AutoWired 필드명으로 하위 클래스 선택
@Service
public class FileService {
private FileUploader awsS3Uploader;
public FileService(FileUploader awsS3Uploader) {
this.awsS3Uploader = awsS3Uploader;
}
}- 필드명 또는 생성자 주입의 경우 파라미터 명으로도 주입할 클래스를 지정할 수 있음
@Qualifier에서 했던 방식과 유사하게 주입하려는 클래스에서 앞글자만 소문자로 설정하는 것이 기본값 @Component에 별도로 이름을 지정한 경우, 해당 이름과 일치하는 변수명을 생성자 파라미터로 사용해야 정상적으로 주입된다.@Component("aws")일경우 변수명 혹은 파라미터명으로 해도 해당 클래스가 주입됨- 우선순위가 가장 낮음
@Qualifier@Primary둘 다 매칭되지 않으면@Autowired필드명으로 주입
4. @Configuration 에서 @Bean 을 등록
@Configuration
public class AppConfiguration {
@Bean
public FileUploader getFileUploader(){
return new AwsS3Uploader();
}
}
// @Component 어노테이션 제거
public class AwsS3Uploader implements FileUploader {
// ... 생략
}
// @Component 어노테이션 제거
public class LocalFileUploader implements FileUploader{
// ... 생략
}- 가장 원초적인 방식
- 컴포넌트 스캔 대상인 어노테이션 (@Controller,@Service,@Component 등...) 은 제거 해야 꼬이지 않음
- @Qualifier 를 사용할 때 bean으로 등록한 메서드 명으로 등록가능
- 추상 클래스를 리턴해주는
@Bean을 설정하고 리턴값에 구현클래스를 지정 @Primary와 연계하여 구현클래스를 리턴하는@Bean을 여러 개 만든 후 우선순위 지정 가능- Configuraion에서 전체의 의존성을 관리하기 때문에 앞에서
@Qualifier,@Primary등과 같이 지정하는 거에 비해 관리 용이
5. Collection Based Injection
@Service
public class FileService {
private Map<String,FileUploader> fileUploader;
public FileService(Map<String, FileUploader> fileUploader) {
this.fileUploader = fileUploader;
}
public String test(){
return fileUploader.get("localFileUploader").fileUpload(null);
}
}- 배열 Collection 객체 등을 통해 구현클래스를 모두 주입하여 필요한 구현클래스 참조하여 사용
- Map의 경우 String Key 값에 구현 클래스의 앞글자만 소문자로 바꾼 값이 기본값이어도 이것도
@Component에서 이름을 지정하면 그 값이 Key 값이 됨 - 위에서 했던 방식들과 주입될 클래스를 사전에 지정하는 방식이 아닌 유연하게 하위 클래스에 접근 가능
- Map 뿐만 아니라 Set, List 도 가능하나 Map 이 접근하기가 편함
🎬 결론
인터페이스 기반의 의존성 주입(DI) 시, 구현 클래스가 2개 이상 존재하면 어떤 구현체를 주입할지 스프링이 판단하지 못해서 에러가 발생
우선순위 메서드 특징 1순위 @Qualifier가장 명시적인 방식, 이름으로 특정 구현체 지정 2순위 @Primary기본(default)으로 주입할 구현체 설정 3순위 @Autowired필드명클래스명 기반 자동 매칭, 우선순위 가장 낮음
- 구현체가 많고, 유연한 전략 전환이 필요하거나 플러그인 구조처럼 동적으로 선택해야 할 경우엔 Map, List, Set 등의 컬렉션 기반 주입(Collection-based injection)을 사용하면 확장성과 유지보수가 훨씬 좋아짐
- 추상 클래스의 구현체를 동시에 여러 개를 써야 할 상황이 올경우 Collection Based Injection 을 사용하여 Map과 같은 Collection 객체에서 관리 가능
- Java Config (@Configuration)를 통해 명시적으로 Bean을 등록하고 관리하는 방식도 고려해 볼 수 있음
- 이는 전체 의존성을 한눈에 파악하고 제어할 수 있는 장점이 있음