[토비의 스프링] 1.7 의존관계 주입(DI)
우선 의존관계부터 알아보자.

이렇게 A가 B를 사용하면 A가 B에게 의존하는 것이다.
여기서 의존한다는게 무슨 의미일까?
B가 변하면 A에게 영향을 미친다는 것이다.
B의 기능이 변화하면 당연히 B를 사용하는 A에게 영향을 미친다. 여기서 A가 변화더라도 B는 아무 영향을 받지 않는다. A를 전혀 사용하고 있지 않기 때문이다.
이런 관계 때문에 의존관계는 방향성도 존재한다.

지금의 UserDao가 이렇게 의존하고 있다.

UserDao가 내부에서 이렇게 ConnectionHelper를 사용하기 때문이다.
하지만 여기서는 클래스가 아닌 인터페이스에 의존하고 있다.
이렇게 만들어야 관계가 느슨해지면서 변화에 영향을 덜 받게 된다.
런타임시에 의존관계가 만들어지는 경우도 있다.
UserDao와 ConnectionMaker등의 설계와 코드에서 드러나지 않는다는 말이다.
프로그램이 시작되고 UserDao 오브젝트가 만들어지고 나서 런타임 시에 의존관계를 맺는 대상을 의존 오브젝트라고 말한다.
의존관계 주입은 이렇게 구체적인 의존 오브젝트와 그것을 사용할 주체, 보통 클라이언트라고 부르는 오브젝트를 런타임 시에 연결해주는 작업을 말한다.
정리하면 의존관계 주입은 다음과 같은 세가지 조건을 충족하는 작업을 말한다.
- 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
- 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3의 존재가 결정한다.
- 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로써 만들어진다.
의존관계 주입은 설계 시점에서 알지 못했던 두 오브젝트가 관계를 맺도록 제 3의 존재가 도와주고, 이걸 IoC 컨테이너 같은 존재가 해준다고 볼 수 있다.
public class UserDao {
private ConnectionHelper connectionHelper;
public UserDao(ConnectionHelper connectionHelper) {
this.connectionHelper = connectionHelper;
}
}
이렇게 의존관계를 주입해주는 것이다.
코드를 작성하는 과정에서는 인터페이스로만 작업을 하고, 런타임시에 ConnectionHelper를 구현한 구현체를 생성자를 통해 주입해주는 것이다.
이렇게 주입하는 것이 아니라, 스스로 검색을 해서 관계를 맺어주는 의존관계 검색이라고 불리는 것도 있다.
의존관계 검색은 자신이 필요로하는 의존 오브젝트를 능동적으로 찾는다.
다음과 같은 UserDao 생성자는 의존관계 검색을 사용하는 것이다.
public UserDao(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
this.connectionHelper = context.getBean("connectionHelper", ConnectionHelper.class);
}
이렇게 이름과 타입을 사용하여 원하는 의존관계를 가져오는 것이다.
자 이제 기존의 문제였던 상황을 살펴보자.
기존에는 데이터베이스의 연결 정보를 변경하기 위해서, 코드가 작성된 모든 부분을 수정했을 것이다.
하지만 DI 방식을 사용해 다음과 같이 구현했다면
@Bean
public ConnectionMaker connectionMaker(){
return new LocalDBConnectionMaker();
}
이제 이 연결 정보를 변경하기 위해서, 이렇게 한 줄만 수정하게 될 것이다.
@Bean
public ConnectionMaker connectionMaker(){
return new ChangedDBConnectionMaker();
}
이런 의존관계 주입에는 3가지 방법이 있다.
- 생성자 주입
- 필드 주입
- setter 주입
이 중에서 가장 권장되는 방식은 생성자 주입이라고 한다.(스프링의 공식 의견이라고...)