본문 바로가기

토비의 스프링 정리

토비의 스프링 - 1.7 의존관계 주입(DI)

//의존관계 검색 
public UserDao(){ 
    DaoFactory daoFactory = new DaoFactory(); //daoFactory가 미리 정의한 이름 
    this.connectionMaker = daoFactory.connectionMaker(); 
} 

//의존관계 주입 
public UserDao(ConnectionMaker connectionMaker){ 
    this.connectionMaker = connectionMaker(); 
}

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

  • IoC 단어는 스프링 서블릿 컨테이너처럼 서버에서 동작하는 서비스 컨테이너라는 뜻인지, 단순희 IoC 개념이 적용된 템플릿 메소드 패턴을 이용해 만들어진 프레임워크인지, IoC 특징을 지닌 기술인지 해석하기 애매함
  • 의존관계 주입(Dependency Injection)은 의도가 명확한 이름
  • 스프링 IoC 기능의 대표적인 동작원리는 주로 의존관계 주입이라고 불림
  • IoC 컨테이너라 불리는 스프링은 의존관계 주입 컨테이너 또는 DI 컨테이너라 불림
  • DI는 오브젝트 레퍼런스를 외부로부터 제공(주입)받아 이를 통해 여타 오브젝트와 동적 의존관계를 만드는 것이 핵심

1.7.2 의존관계

  • 의존관계는 항상 방향성이 있음
  • A가 B를 의존하고 있으면 아래와 같은 다이어그램으로 나타낼 수 있음
  • A가 B에 의존하고 있지만, 반대로 B는 A에 의존하지 않는다.

17.3 UserDao의 의존관계

  • UserDao는 ConnectionMaker 인터페이스에만 의존하고 있음
  • 인터페이스 구현 클래스와는 느슨한 결합을 가짐
  • 인터페이스를 통해 의존관계를 제한하면 인터페이스 구현 클래스 변경에 자유롭게 됨
  • UserDao는 DConnectionMaker 클래스에 의존하지 않음
  • UML 상에서 나타나는 코드간 의존관계 외에 런타임 시에 오브젝트 사이에서 만들어지는 의존관계가 있음. 이를 런타임 의존관계 또는 오브젝트 의존관계라 함
  • 런타임 시 의존관계를 맺는 대상(오브젝트)을 의존 오브젝트(Dependent Object)라 함

💡 의존관계 주입은 런타임시에 오브젝트를 연결하는 것임

 

1.7.4 의존관계 주입 세 가지 조건

  1. 인터페이스에만 의존을 해야함
    • 코드에서 런타임 시점의 의존관계가 드러나지 않음
  2. 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재결정
    • 스프링에서의 애플리케이션 컨텍스트, 빈 팩토리, IoC 컨테이너 등이 제3의 존재임
  3. 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어짐

1.7.5 UserDao의 의존관계 주입

  • DaoFactory는 오브젝트의 생성, 초기화, 제공 등의 작업을 수행하는 컨테이너임
  • 따라서, 의존관계 주입을 담당하는 컨테이너이기 때문에 DI 컨테이너라 할 수 있음
  • DaoFactory라는 제3의 존제가 런타임 시 의존관계 설정
  • 파라미터를 통해 레퍼런스(new DConnectionMaker) 제공
  • DI 컨테이너에 의해 런타임 시 의존 오브젝트를 사용할 수 있도록 그 레퍼런스를 전달받는 과정이 마치 메소드(생성자)를 통해 DI 컨테이너가 UserDao에게 주입해 주는것과 같다고 하여 이를 의존관계 주입이라 함
  • 코드 레벨의 의존관계
  • 런타임 시의 의존관계

 💡 DI는 자신이 사용할 오브젝트에 대한 선택과 생성 제어권을 외부로 넘기고 자신은 수동적으로 주입받은 오브젝트를 사용한다는 점에서 IoC 개념과 잘 맞음

 

1.7.6 의존관계 검색과 주입

  • 의존관계 검색(DL, Dependency Lookup)은 런타임 시 의존관계를 맺을 오브젝트를 결정하는 것과 오브젝트의 생성작업은 외부 컨테이너에게 IoC로 맡기지만, 이를 가져올 때 메소드나 생성자를 통한 주입 대신 스스로 컨테이너에게 요청하는 방법을 사용함
  • 애플리케이션 컨텍스트가 미리 정의해 놓은 이름을 전달해서 그 이름에 해당하는 오브젝트를 찾는 것이 의존관계 검색임
  • 의존관계 검색은 의존관계 주입의 거의 모든 장점을 가지고 있음. 하지만, 코드 상으로 의존관계 주입이 더욱 단순 및 깔끔함
  • 의존관계 검색을 사용할 때가 있음. 애플리케이션의 기동 시점에서 적어도 한 번은 의존관계 검색 방식을 사용해 오브젝트를 가져와야함. 스태틱 메소드인 main()에는 DI를 이용해 오브젝트를 주입받을 방법이 없기 때문.
  • 서블릿은 스프링이 미리 만들어서 제공하기 때문에 직접 구현할 필요 없음
  • 의존관계 검색의 오브젝트는 의존관계 주입과 달리 스프링의 빈일 필요없음
//의존관계 검색 
public UserDao(){ 
    DaoFactory daoFactory = new DaoFactory(); //daoFactory가 미리 정의한 이름 
    this.connectionMaker = daoFactory.connectionMaker(); 
} 

//의존관계 주입 
public UserDao(ConnectionMaker connectionMaker){ 
    this.connectionMaker = connectionMaker(); 
}

💡 DI를 원하는오브젝트는 먼저 자기 자신이 컨테이가 관리하는 빈이 돼야 함

 

1.7.7 의존관계 주입의 응용 및 기능 구현의 교환, 부가기능 추가

  • 스프링이 제공하는 기능의 99%가 DI의 혜택을 이용하고 있음
  • 의존관계 주입을 이용하면 부가기능을 쉽게 추가할 수 있음
  • 의존하는 인터페이스 사이에 새로운 인터페이스 구현 클래스를 추가하면 부가기능을 쉽게 추가 할 수 있음
  • DB의 연결 횟수를 카운트 하는 로직을 추가할 때 UserDao와 ConnectionMaker 사이에 ConnectionMaker을 구현하는 CountingConnectionMaker 클래스를 추가함
public class CountingConnectionMaker implements ConnectionMaker {
    int cnt = 0;
    private ConnectionMaker realConnectionMaker;

    public CountingConnectionMaker(ConnectionMaker connectionMaker) {
        realConnectionMaker = connectionMaker;
    }

    @Override
    public Connection getConnection() throws ClassNotFoundException, SQLException {
        this.cnt++;
        return realConnectionMaker.getConnection();
    }

    public int getCnt() {
        return cnt;
    }
}

@Configuration
public class DaoFactory {
    @Bean
    public UserDao userDao(){
        return new UserDao(connectionMaker());
    }
    
    @Bean
    public ConnectionMaker connectionMaker() {
        return new CountingConnectionMaker(realConnectionMaker());
    }

    @Bean
    public DConnectionMaker realConnectionMaker() {
        return new DConnectionMaker();
    }
}

public class UserDaoTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        //UserDao dao = new DaoFactory().userDao();
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(DaoFactory.class);
        UserDao dao = applicationContext.getBean("userDao", UserDao.class);

        CountingConnectionMaker ccm = applicationContext.getBean("connectionMaker",
                CountingConnectionMaker.class);
        System.out.println(ccm.getCnt());
        ...
    }
}

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

  1. 수정자(setter) 메소드를 이용한 주입
    • 한 번에 하나의 파라미터 밖에 없음
    • 파라미터로 전달된 값을 인스턴로 변수로 저장
    • DI 받을 오브젝트에 set을 추가하여 메소드를 명명 하는게 일반적 관례
    @Configuration
    public class DaoFactory {
        @Bean
        public UserDao userDao() {
            UserDao userDao = new UserDao();
            userDao.setConnectionMaker(connectionMaker());
            return userDao;
        }
    }
  2. 일반 메소드를 이용한 주입
    • 여러 개의 파라미터를 가짐
    • 실수가 생길 수 있음