쌍문동 척척박사
Spring / ReactJS / PS

IoC : 오브젝트와 의존관계

IoC : 오브젝트와 의존관계

관련 포스트 : AOP

관련 포스트 : PSA


제어의 역전

제어의 역전 (Inversion of Control)이란, 말 그대로 제어 흐름이 바뀌는 것을 말한다. 제어가 역전된 오브젝트는 자신이 어떻게 만들어지고 어디서 사용되는지 알 수 없으며, 자신이 사용할 오브젝트를 직접 생성하지도 않는다. 모든 제어 권하을 다른 대상 (ex 팩토리, 컨테이너)에게 위임하기 때문이다.

1
2
3
4
5
6
7
8
public class UserDaoTest{
  // ...
  public void test(){
    ApplicationContext context
      = new AnnotationConfigApplicationContext(DaoFactory.class);
    UserDao dao = context.getBean("userDao", UserDao.class);
  }
}

프레임워크도 IoC 개념이 적용된 대표적인 기술이다. 단순히 라이브러리만을 사용하는 어플리케이션 코드는 어플리케이션의 흐름을 직접 제어한다. 하지만 프레임워크를 사용하는 경우 어플의 흐름은 프레임워크에 의해 주도되며, 어플리케이션 코드는 프레임워크에 의해 사용된다. 따라서 IoC 개념이 적용됐는지 여부로 라이브러리와 프레임워크를 구분지을 수 있다.

오브젝트 팩토리

객체의 생성 방법을 결정하고, 그렇게 만들어진 오브젝트를 반환하는 객체를 팩토리라 한다. 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 깔끔하게 분리하려는 목적으로 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DaoFactory {

  @Bean
  public UserDao userDao(){
    ConnectionMaker connectionMaker = new DConnectionMaker();
    UserDao userDao = new UserDao(connectionMaker);
    return userDao;
  }
  
  @Bean
  public ConnectionMaker connectionMaker(){
    return new DConnectionMaker();
  }
}

2.png

IoC 컨테이너와 DI

스프링에서도 IoC의 개념이 적용되어, 스프링이 객체를 직접 생성하고 관리한다. 이때의 객체를 빈 (Bean)이라 한다.

  • Bean : 스프링이 제어권을 가지고 생성 및 관계를 부여하는 오브젝트. 스프링의 모든 객체가 빈은 아니다.
  • BeanFactory : 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트

스프링에서 오브젝트 팩토리에 대응되는 것이 ApplicationContext이다. 엄밀히 말하면 어플리케이션 컨텍스트는 BeanFactory 인터페이스를 상속한 빈 팩토리 인터페이스의 일종으로, 빈 팩토리 역할 외에도 여러 기능을 제공하는 서브 인터페이스다. 하지만 IoC의 역할이 가장 크기 때문에 어플리케이션 컨텍스트 인터페이스를 구현한 오브젝트를 IoC 컨테이너 or 스프링 컨테이너라고 부른다.

3.png

Singleton과 Bean Scope

오브젝트 팩토리와 어플리케이션 컨텍스트의 또 다른 차이는 빈 생성 방식에서 나타난다. 스프링에서는 빈이 생성되고, 존재하고, 적용되는 범위를 Bean의 Scope라고 한다. 특정 빈 (오브젝트)을 여러 번 호출할 때 오브젝트 팩토리는 매번 새로운 오브젝틀르 생성해 반환하는 Prototype Scope 방식으로 동작하지만, 어플리케이션 컨텍스트는 최초에 생성된 빈 1개만을 동일하게 반환하는 Singleton Scope 방식으로 동작한다. 즉 스프링 컨테이너는 기본적으로 빈을 싱글톤 스코프로 저장하고 관리하는 싱글톤 레지스트리이기도 하다.

스프링이 빈을 싱글톤으로 관리하는 이유는 운영 환경에 있다. 스프링은 처음 설계부터 대규모 엔터프라이즈 서버환경을 목표로 구축되어 높은 성능을 요구하는 환경이었다. 그러다보니 수 많은 클라이언트로부터의 요청이 올때마다 매번 로직을 담당하는 오브젝트를 생성하면 서버에 부하가 심해지기 때문에 어플리케이션 안에 제한된 수, 대개 1개의 오브젝트만 만들어 사용하기 위해 싱글톤 패턴을 선택했다.

하지만 직접 빈을 싱글톤으로 관리하는 일은 쉬운 일이 아니다. 객체의 생성부터 관계 설정, 사용 등 제어할 동작들이 많은데, IoC 컨테이너에게 이 모든 권한과 제어권을 넘기면 컨테이너에 의해 빈이 자동으로 관리된다. 정리하자면 스프링은 안정적인 서버 운영을 위해 싱글톤 패턴을 선택했고, 싱글톤 패턴을 실현하기 위해 IoC 방식을 선택해 IoC 컨테이너에게 의존관계 주입 (DI)에 대한 권한을 부여했다.

의존관계 주입 (DI)

의존관계는 다음과 같이 특정 객체에서 다른 객체를 필요로 하는, 다시 말해 다른 객체의 변수나 메소드 등을 사용하는 관계를 말한다. 의존 관계에는 항상 방향성이 있다. A는 B에 의존하고 있지만, B는 A에 의존하지 않는다.

4.png

이때 의존하는 객체 (A)가 사용할 실제 사용대상인 오브젝트 (B)를 의존 객체 (Dependent Object) 라고 한다. 그리고 구체적인 의존 오브젝트와 그것을 사용할 주체를 런타임 시 연결해주는 작업을 의존관계 주입 (Dependency Injection) 이라 한다. 쉽게 말해 의존 오브젝트를 객체로 넘겨주는 것을 말한다. 의존관계 주입은 IoC 컨테이너에 의해 이루어진다.

1
2
3
4
5
6
7
8
public class BookService {  // 의존 객체를 사용할 주체 객체
  
  private BookRepository bookRepository;  // 의존 객체
  
  public BookService(BookRepository bookRepository){
    this.bookRepository = bookRepository; // 의존관계 주입
  }
}

참고

최근에는 IoC 컨테이너를 스프링의 의존관계 주입이라는 핵심적 역할에 포커스를 두어 DI 컨테이너라 부르기도 한다.

그런데 의존관계에는 모델이나 코드에서 클래스와 인터페이스를 통해 드러나는 의존관계 말고, 런타임 시 오브젝트 사이에서 만들어지는 의존관계도 있다. 설계 시점의 의존관계가 실체화된 것으로, 이를 런타임 의존관계 or 오브젝트 의존관계라고 한다.

1
public interface ConnectionMaker { //... }
1
public class DConnectionMaker implements ConnectionMaker { //... }
1
2
3
4
5
6
7
public class UserDao{

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

5.png

위 상황에서 명시적인 의존 관계는 UserDao와 ConnectionMakerdlek. 하지만 ConnectionMaker는 interface이고, 실제 구현체 (의존 객체)는 DConnectionMaker이므로 정확히는 DConnectionMaker 오브젝트의 레퍼런스가 전달되야 한다. DI 컨테이너가 이런 런타임 의존관계를 파악해 실제 사용할 의존 오브젝트를 주입시켜 준다.

6.png

참고 자료

토비의 스프링 3.1

comments powered by Disqus