시작하기에 앞서,
스프링에서 연관관계를 주입할 때 해당 타입으로 된 객체가 많을 때 어떻게 처리할까?
예를 들자면, A 인터페이스에 구현체가 B, C라고 하자.
그리고 D라는 인터페이스를 구현한 E 객체가 B를 주입받고자 하는데, DIP를 준수하여 A를 주입받으려 한다.
하지만, A라는 인터페이스에는 B, C 두 개가 있고 스프링은 scan하면서 두 개를 발견하게 되어 문제가 생길 것이다.
이럴 때 어떻게 해결 할 수 있을까?
빈 2개 이상
살펴보기에 앞서, 다음과 같은 예제를 살펴보자
public interface EmployeeRepository {
void save(Employee employee);
Employee findById(Long employeeId);
List<Employee> findAll();
}
@Component
public class EmployeeRepositoryImplement implements EmployeeRepository {
private static Long sequence = 0L;
private Map<Long, Employee> store = new HashMap<>();
@Override
public void save(Employee employee) {
employee.setId(++sequence);
store.put(employee.getId(), employee);
}
@Override
public Employee findById(Long employeeId) {
return store.get(employeeId);
}
@Override
public List<Employee> findAll() {
return new ArrayList<>(store.values());
}
}
@Component
public class EmployeeRepositoryImplement2 implements EmployeeRepository {
private static Long sequence = 0L;
private Map<Long, Employee> store = new HashMap<>();
@Override
public void save(Employee employee) {
employee.setId(++sequence);
store.put(employee.getId(), employee);
}
@Override
public Employee findById(Long employeeId) {
return store.get(employeeId);
}
@Override
public List<Employee> findAll() {
return new ArrayList<>(store.values());
}
}
현재 Repository의 구현체로 다음과 같이 두 개가 있다고 가정하자.(실제로 두 개 같은 코드이지만 다르다고 가정하자.)
그런데 우리는 두 개 다 사용해야한다고 하자.
그렇다면 이 Repository를 주입받는 객체는 어떻게 될까?
이를 살펴보기 위해 Repository를 주입받는 Service를 보자.
public interface EmployeeService {
void join(Employee employee);
Employee findEmployee(Long employeeId);
List<Employee> findAllEmployee();
}
@Component
public class EmployeeServiceImplement implements EmployeeService {
public final EmployeeRepository employeeRepository;
@Autowired
public EmployeeServiceImplement(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@Override
public void join(Employee employee) {
employeeRepository.save(employee);
}
@Override
public Employee findEmployee(Long employeeId) {
return employeeRepository.findById(employeeId);
}
@Override
public List<Employee> findAllEmployee() {
return employeeRepository.findAll();
}
public EmployeeRepository getEmployeeRepository() {
return employeeRepository;
}
}
여기서 EmployeeRepository라는 추상체로 주입을 받고 있는데 우리는 EmployeeRepository를 구현하는 구현체가 2개이다.
이 상태에서 돌리면 어떤 결과가 발생할까?
다음과 같이 1개를 예상했으나 2개를 찾았다는 오류 메세지를 만든다.
그렇다면 이를 해결하는 방법을 알아보자.
(1) 이름 맞추기
@Autowired를 활용하여 해결하는 것은 의외로 간단하다. 단순히 사용하는 구현체로 이름을 맞춰주면 된다.
@Autowired
public EmployeeServiceImplement(EmployeeRepository employeeRepositoryImplement) {
this.employeeRepository = employeeRepositoryImplement;
}
(2) @Qualifier
@Qaulifier를 활용하여 다음과 같이 해결한다.
@Component
@Qualifier("Main")
public class EmployeeRepositoryImplement implements EmployeeRepository {
private static Long sequence = 0L;
private Map<Long, Employee> store = new HashMap<>();
@Override
public void save(Employee employee) {
employee.setId(++sequence);
store.put(employee.getId(), employee);
}
@Override
public Employee findById(Long employeeId) {
return store.get(employeeId);
}
@Override
public List<Employee> findAll() {
return new ArrayList<>(store.values());
}
}
@Autowired
public EmployeeServiceImplement(@Qualifier("Main") EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
사용할 객체 위에 @Qaulifier를 명시하고 이름을 함께 명시한 뒤 주입할 때 이를 명시해주면 된다.
(3) @Primary
@Primary를 활용하여 우선 순위를 지정하여 @Primary를 지정한 객체를 항상 우선으로 주입하도록 한다.
@Component@Primarypublic class EmployeeRepositoryImplement implements EmployeeRepository { private static Long sequence = 0L; private Map<Long, Employee> store = new HashMap<>(); @Override public void save(Employee employee) { employee.setId(++sequence); store.put(employee.getId(), employee); } @Override public Employee findById(Long employeeId) { return store.get(employeeId); } @Override public List<Employee> findAll() { return new ArrayList<>(store.values()); }}
@Autowiredpublic EmployeeServiceImplement(EmployeeRepository employeeRepositoryImplement) { this.employeeRepository = employeeRepositoryImplement;}
다음과 같이, 우선으로 주입할 객체에 @Primary로 설정한 뒤 원래 주입하던 방식대로 진행하면 잘 주입이 된다.
(4) @Qualifier vs @Primary
보통 @Primary는 간단하게 쓸 수 있기에 많이 사용되어진다.
어떤 객체의 사용이 독보적이고 다른 객체들의 사용이 떨어진다면 이러한 @Primary를 사용하는 것이 유리할 것이다.
만약 다른 객체를 사용하고 싶다면 @Qaulifier를 지정하여 사용하면 된다.
@Qaulifier가 우선 순위가 더 높기 때문에 @Primary가 적용될 걱정은 하지 않아도 된다.
만약, 같은 타입의 객체가 등록되어야 할 때는 자주 사용하는 것을 @Primary로 하여 기본적으로 사용하되 필요할 때 서브 객체를 @Qaulifier로 지정하여 사용할 수 있도록 하자.
하지만 만약 두 개 다 필요한 경우라면 어떨까?
두 개를 다 주입받으려면 어떻게 해야할까?
바로 List나 Map으로 주입받으면 된다.
예제로 바로 살펴보자
위와 같이 EmployeeRepositoryImplement와 EmployeeRepositoryImplement2가 있다고 했을 때 다음과 같이 받으면 된다.
public class Container_Bean { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoConfig.class, ES.class); @Test public void Container_Bean() throws Exception { ES employeeService = ac.getBean(ES.class); Map<String, EmployeeRepository> employeeRepositoryMap = employeeService.employeeRepositoryMap; System.out.println("Map"); System.out.println("===================="); for (String s : employeeRepositoryMap.keySet()) { System.out.println("s = " + s + ", " + employeeRepositoryMap.get(s)); } System.out.println("===================="); System.out.println(); List<EmployeeRepository> employeeRepositoryList = employeeService.employeeRepositoryList; System.out.println("List"); System.out.println("===================="); for (EmployeeRepository employeeRepository : employeeRepositoryList) { System.out.println("employeeRepository = " + employeeRepository); } System.out.println("===================="); } static class ES { public final List<EmployeeRepository> employeeRepositoryList; public final Map<String, EmployeeRepository> employeeRepositoryMap; @Autowired public ES(List<EmployeeRepository> employeeRepositoryList, Map<String, EmployeeRepository> employeeRepositoryMap) { this.employeeRepositoryList = employeeRepositoryList; this.employeeRepositoryMap = employeeRepositoryMap; } }}
다음과 같이 AutoConfig.class를 빈으로 등록하고 아래 임의로 설정한 ES.class 를 빈으로 등록한 뒤, 이를 통해 간단하게 ES에 EmployeeRepositoryImplement와 EmployeeRepositoryImplement2를 List와 Map으로 주입하는 것을 해보았다.
결과를 보면 다음과 같이 잘 나온 모습을 확인할 수 있다.
다음과 같이 중복으로 불러와 사용하고 싶을 때, List와 Map으로 불러와 잘 사용하면 될 것이다.
'스프링' 카테고리의 다른 글
MVC, Spring MVC (0) | 2021.09.18 |
---|---|
빈 초기화 콜백, 소멸전 콜백 (0) | 2021.09.16 |
수동 빈 등록, 자동 빈 등록 (2) | 2021.09.15 |
스프링 컨테이너, 빈 (0) | 2021.09.15 |
스프링의 핵심 (0) | 2021.09.14 |