Spring에 대한 공부를 하며, Spring에서 제공하는 DI 덕분에 객체지향 설계 원칙인 SOLID 중 지켜지지 않던 O(OCP), D(DIP)를 지키며 개발할 수 있다는 것에 공감할 수 있었다. 문득, DI에 대해 공부해보며 궁금증이 생긴 부분이 있었고 이를 Spring에서 어떻게 해결해주는지에 대하여 알아본 결과를 정리해보았다.
궁금증....만약 인터페이스 1개에 구현체가 2개있는 경우는 의존성 주입이 어떻게 될까??
실제 코드를 통해 알아 보았다. 아래와 같은 Car라는 Interface가 있다.
public interface Car {
int go();
}
해당 인터페이스를 구현하는 구현체 k5, k7이 아래와 같이 작성되있다고 가정해보자. 참고로 @Compenent의 경우 해당 클래스를 Spring Bean으로 등록한다는 것을 말한다.
@Component
public class k5 implements Car{
@Override
public int go() {
return 5;
}
}
@Component
public class k7 implements Car{
@Override
public int go() {
return 7;
}
}
그리고 테스트를 진행해 보았다.
@Autowired
private Car car;
참고로, @Autowired의 경우 Car Interface에 맞는 Bean으로 등록 된 객체를 가져와 Spring에서 자동으로 DI를 해달라고 명시해주는 어노테이션이다. 하지만, car 부분에 빨간줄이 생기며 제대로 DI가 안된다는 것을 알 수 있었다.
원인의 에러는 다음과 같았다.
Could not autowire. There is more than one bean of 'Car' type.
Beans: k5 (k5.java)
k7 (k7.java)
직역해보면 autowire할 수 없다. 'Car' type의 bean이 한개보다 많다. 라고 한다.
그렇다. 구현체가 두개 이상 있는 경우에는 역시나 DI를 제대로 하지 못한다.
그렇다면, 해결 방법으로는 어떤 것이 있을까??
- 변수 네이밍을 Bean 이름으로 한다.
- @qualifier("Bean 객체 이름")을 사용한다.
- @primary를 사용한다.
- 사용하는 구현체 1개만 @component를 유지하고 나머지 구현체의 @component를 지워 Bean에 구현체 한개만 등록되도록 한다.
4 가지의 방법을 직접 코드로 구현해보며, 각각의 장단점에 대하여 느껴보자.
1번째 방법을 통해 해결해본 코드이다.
@Autowired
private Car k5;
결과적으로 제대로 DI를 통해 객체를 주입받지만, SOLID의 원칙인 OCP와 DIP가 위배된 코드를 확인할 수 있다.
2번째 방법을 통해 해결해본 코드이다.
@Qualifier("k5")
@Autowired
private Car car;
결과적으로 제대로 DI를 통해 객체를 주입받고, OCP도 지키며 DIP도 지킨다고 볼 수 있다. Spring에서도 @qualifier를 추가하여 문제를 해결하라고 안내하긴 한다. 하지만,, @qualifier("k5")를 사용하다가 후에 k7으로 주입하고 싶으면 @qualifier("k5")
@qualifier("k7") 변경 작업이 불가피하기때문에 결과적으로 보면 OCP와 DIP를 위반한다고 볼 수 있다.
3번째 방법을 통해 해결해본 코드이다.
@Component
@Primary
public class k5 implements Car{
@Override
public int go() {
return 5;
}
}
해당 @primary의 경우 구현체 중 우선순위를 정해준 것이기 때문에, 같은 interface의 구현체 중 1개만 정해야 하는 단점이 있지만, 테스트 클래스에서 수정하는 것이 아닌 구현체 클래스에서 수정하는 것을 볼 수 있다. 또한, OCP와 DIP도 지켜주는 모습이다.
4번째 방법을 통해 해결하는 방법은 사용하는 구현체 딱 한 개 말고는 @component를 삭제하여 Bean에 등록을 안시켜주면 된다. Spring에서 Interface에 의존성 주입을 할 때 1개의 구현체만 있는경우는 문제가 생기지 않기 떄문이다.
결과적으로, 3번째와 4번째 방법이 좋은 해결 방법이라고 볼 수 있다.
가장 좋은 방법은 4번째 방법으로, 사용할 하나의 구현체만 Bean으로 등록하는 것이 좋은 방법인 것 같다.
하지만, 여러개의 구현체를 두고 상황에 맞춰서 각각 다른 구현체를 사용해야 한다면 2번째 방법을 통해 해결하는 것이 최선인 것 같다.
혹시나, 나중에 구현체를 두개이상 두는 경우 해당 방법으로 해결하면 좋을 것 같다.