본문 바로가기

스프링

같은 타입의 빈 등록(중복)

시작하기에 앞서,

스프링에서 연관관계를 주입할 때 해당 타입으로 된 객체가 많을 때 어떻게 처리할까?

예를 들자면, 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으로 주입받으면 된다.

예제로 바로 살펴보자

위와 같이 EmployeeRepositoryImplementEmployeeRepositoryImplement2가 있다고 했을 때 다음과 같이 받으면 된다.

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 를 빈으로 등록한 뒤, 이를 통해 간단하게 ESEmployeeRepositoryImplementEmployeeRepositoryImplement2를 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