PengTory

[Spring] 오브젝트와 의존관계 (5) _ DI 본문

Spring

[Spring] 오브젝트와 의존관계 (5) _ DI

펭토리 2022. 10. 14. 02:44

제어의 역전(IoC)과 의존관계 주입

IoC는 매우 느슨하게 정의돼서 폭넓게 사용되는 용어다. 따라서 스프링을 IoC 컨테이너라고만 해서는 스프링에 제공하는 기능의 특징을 명확하게 설명할 수 없다. 스프링이 제공하는 IoC 방식을 핵심을 짚어주는 의존관계 주입(Dependency Injection)라는, 좀더 의도가 명확히 드러나는 이름을 사용하기 시작했다.

 

런타임 의존관계 설정

의존관계

두 개의 클래스 또는 모듈이 의존관계에 있다고 말할 때는 항상 방향성을 부여해줘야한다. 

A가 B에 의존하고 있음

의존한다는건 B가 변하면 A에 영향을 미친다는 뜻이다. A가 B에 의존하고 의존하고 있지만, 반대로 B는 A에 의존하지 않는다.

의존하지 않는다는 말은 B는 A의 변화에 영향을 받지 않는다는 뜻이다.

 

지금까지 작업해왔던 UserDao를 예시로 들어보겠다. 현 상황은 아래와 같다.

인터페이스를 통한 느슨한 결합을 갖는 의존관계

위 사진을 보면 UserDao가 ConnectionMaker에 의존하고 있는 것을 알 수 있다. 그런데 모델이나 코드에서 클래스와 인터페이스를 통해 드러나는 읜존관계 말고, 런타임 시에 오브젝트 사이에서 만들어지는 의존관계도 있다. 런타임 의존관계 또는 오브젝트 의존 관계인데, 설계 시점의 의존관계가 실체화된 것이라고 볼 수 있다. 런타임 의존관계는 모델링 시점의 의존관계와는 성격이 분명히 다르다.

인터페이스를 통해 설계 시점에 느슨한 의존관계를 갖는 경우에는 UserDao의 오브젝트가 런타임 시에 사용할 오브젝트가 어떤 클래스로 만든 것인지 미리 알수 없다. 프로그램이 시작되고 UserDao 오브젝트가 만들어지고 나서 런타임 시에 의존관계를 맺는 대상, 즉 실제 사용 대상인 오브젝트를 의존 오브젝트(dependent object)라고 부른다.

의존관계 주입이란 다음과 같이 세 가지 조건을 충족하는 작업을 말한다.

  • 클래스 모데이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
  • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3자의 존재가 결정한다.
  • 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입) 해줌으로써 만들어진다.

UseDao의 의존관계 주입

public UserDao(){
    connectionMaker = new DConectionMaker();
}

이 코드의 문제는 이미 런타임 시의 의존 관계가 코드 속에 다 미리 결정되어 있다는 점이다.

그래서 IoC 방식을 써서 UserDao로부터 런타임 의존관계를 드러내는 코드를 제거하고, 제3의 존재에 런타임 의존관계 결정 권한을 위임한다. 그래서 최종적으로 DaoFactory라는 것이 만들어졌다.

DaoFactory는 런타임 시점에서 UserDao가 사용할 ConnectionMaker 타입의 오브젝트를 결정하고 이를 생성한 후 UserDao의 생성자 파라미터로 주입해 UserDao가 DConnectionMaker의 오브젝트와 런타임 의존관계를 맺게 해준다.

따라서 위 세 가지 조건을 모두 충족했기 때문에 의존관계 주입(DI)를 사용했다고 볼 수 있다.

public class UserDao {
    private ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker){
        this.connectionMaker = connectionMaker;
    }

}

이렇게 두 개의 오브젝트 간 런타임 의존관계가 만들어졌다.

런타임 시의 의존관계 주입과 사용 의존관계

 

의존관계 검색과 주입

스프링이 제공하는 IoC 방법에는 의존관계 주입만 있는 것이 아니다.

코드에서는 구체적인 클래스에 의존하지 않고, 런타임 시에 의존관계를 결정한다는 점에서 의존관계 주입과 비슷하지만, 의존관계를 맺는 방법이 외부로부터의 주입이 아니라 스스로 검색을 이용하기 때문에 의존관계 검색 이라고 불리는 것도 있다.

의존관계 검색은 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾는다.

스프링의 IoC 컨테이너인 애플리케이션 컨텍스트는 getBean()이라는 메소드를 제공한다. 바로 이 메소드가 의존관계 검색에 사용되는 것이다.

 

의존관계 검색 VS 의존관계 주입

의존관계 검색은 기존 의존관계 주입의 거의 모든 장점을 가지고 있다. IoC 원칙에도 잘 들어맞는다. 단, 방법이 조금 다르다.

코드 면에서는 의존관계 주입  쪽이 훨씬 단순하고 깔끔하다. 대게는 의존관계 주입 방식을 사용하는 편이 낫다.

그런데 앞에서 작성한 테스트코드처럼 의존관계 검색 방식인 getBean()을 사용한 경우가 있다. 스태틱 메소드인 main()에서는 DI를 이용해 오브젝트를 주입받을 방법이 없기 때문이다. 서버에서도 마찬가지다. 이와 같은 경우에는 의존관계 검색을 사용해야한다.

의존관계 검색과 의존관계 주입을 적용할 때 발견할 수 있는 중요한 차이점은 의존관계 검색 방식에서 검색하는 오브젝트는 자신이 스프링의 빈일 필요가 없다는 점이다. 반면에 의존관계 주입에서는 UserDao와 ConnectionMaker 사이에 DI가 적용되려면 UserDao도 반드시 컨테이너가 만드는 빈 오브젝트여야 한다.

DI를 원하는 오브젝트는 먼저 자기 자신이 컨테이너가 관리하는 빈이 돼야 한다는 사실을 잊지 말자!

 

메소드를 이용한 의존관계 주입

지금까지는 의존관계 주입을 위해 생성자를 사용했다. 생성자가 아닌 일반 메소드를 이용해 의존 오브젝트와의 관계를 주입해주는 방법에는 크게 두가지가 있다.

 

1) 수정자 메소드를 이용한 주입

수정자(setter) 메소드는 외부에서 오브젝트 내부의 애트리뷰트 값을 변경하려는 용도로 주로 사용된다. 수정자 메소드는 외부로부터 제공받은 오브젝트 레퍼런스를 저장해뒀다가 내부의 메소드에서 사용하게 하는 DI 방식으로 활용하기에 적당하다.

 

2) 일반 메소드를 이용한 주입

수정자 메소드처럼 set으로 시작해야 하고 한 번에 한개의 파라미터만 가질 수 있다는 제약이 싫다면 여러 개의 파라미터를 갖는 일반 메소드를 DI용으로 사용할 수도 있다. 임의의 초기화 메소드를 이용하는 DI는 적절한 개수의 파라미터를 가진 여러 개의 초기화 메소드를 만들 수 있기 때문에 한 번에 모든 필요한 파라미터를 다 받아야하는 생성자보다 낫다.

 

실제로 스프링은 생성자, 수정자 메소드, 초기화 메소드를 이용한 방법 외에도 다양한 의존관계 주입을 지원한다.

 

 

 

[참고자료] 토비의 스프링 3.1 Vol.1 스프링의 이해와 원리