Intellij Spring - 제어의 역전(IoC)과 의존관계 주입(DI)

3 minute read

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

토비의 스프링 3.1을 공부하면서 정리한 내용입니다.
  • 제어의 역전 (Inversion of Control)

    제어의 역전은 스프링을 통해 많이 알려진 용어이지만, 제어의 역전이란 개념은 오래전부터 있었다. 제어의 역전이란 간단히 프로그램의 제어 흐름 구조가 뒤바뀌는 것이라고 설명할 수 있다.
    일반적으로 프로그램의 흐름은 main() 메소드와 같이 프로그램이 시작되는 지점에서 다음에 사용할 오브젝트를 결정하고, 결정한 오브젝트를 생성하고, 만들어진 오브젝트에 있는 메소드를 호출하고, 그 오브젝트 메소드 안에 다음에 사용할 것을 결정하고 호출하는 식의 작업이 반복된다. 이러한 프로그램 구조에서 오브젝트는 프로그램의 흐름을 결정하거나 사용할 오브젝트를 구성하는 작업에 능동적으로 참여한다.
    제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않고 생성하지도 않는다. 또한 자신도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.
    서블릿의 경우 서블릿을 개발해서 서버에 배포할 수는 있지만, 실제적인 제어는 서블릿에 대한 제어 권한을 가진 컨테이너가 적절한 시점에 서블릿 클래스의 오브젝트를 만들고 그 안의 메소드를 호출한다. 이처럼 서블릿이나 JSP, EJB처럼 컨테이너 안에서 동작하는 구조는 간단한 방식이긴 하지만 제어의 역전 개념이 적용되어 있다고 볼 수 있다.
    프레임워크도 제어의 역전 개념이 적용된 대표적인 기술이다. 프레임워크는 단지 미리 만들어둔 반제품이나, 확장해서 사용할수 있도록 준비된 추상 라이브러리의 집합이 아니다. 라이브러리를 사용하는 애플리케이션 코드는 단지 동작중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용할 뿐 애플리케이션의 흐름은 직접 제어를 한다. 반면 프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다. 프레임워크에는 분명한 제어의 역전 개념이 적용되어 있어야 한다.
    IoC 프레임워크인 스프링 없이도IoC 개념은 적용할 수 있다. IoC는 기본적으로 프레임워크만의 기술도 아니고 프레임워크가 꼭 필요한 개념도 아니다, IoC를 적용함으로써 설계가 깔끔해지고 유연성이 증가하며 확장성이 좋아지기 때문에 필요할 때면 Ioc 스타일의 설계와 코드를 만들어 사용하면 된다.
    하지만 IoC를 애플리케이션 전반에 걸쳐 적용하려면 스프링과 같은 IoC 프레임워크의 도움을 받는 편이 훨씬 유리하다.
  • 의존관계 주입(Dependency Injection)

    IoC라는 용어는 매우 느슨하게 정의돼서 폭넓게 사용되는 용어이기 때문에 스프링을 IoC 컨테이너라고만 해서는 스프링이 제공하는 기능의 특징을 명확하게 설명하지 못한다. 그래서 스프링이 제공하는 IoC 방식의 핵심을 짚어주는 의존관계 주입이라는 이름을 사용하기 시작했다.
    의존관계를 말할 때는 항상 방향성을 부여해야 한다. A와 B가 있을때 B가 변하면 그것이 A에 영향을 미칠때 A가 B에 의존한다고 말한다. 예를 들어 A가 B에 정의된 메소드를 호출해서 사용하는 경우다. 이때 방향성이 있는데 A가 B에 의존하고 있지만, 반대로 B는 A에 의존하지 않는다. 즉 B는 A의 변화에 영향을 받지 않는다.
    UserDao.java
    public class UserDao {
          private ConnectionMaker connectionMaker;
    
          public UserDao() {
                  this.connectionMaker = new MyConnectionMaker();
          }
          ...
    }
    
    UserDao는설계 시점에서 ConnectionMaker라는 인터페이스를 구현한 MyConnectionMaker라는 구체적인 클레스의 존재를 알고 있다. 즉 ConnectionMaker라는 인터페이스의 관계뿐 아니라 런타임 의존관계, 즉 MyConnectionMaker 오브젝트를 사용하겠다는 것까지 UserDao가 결정하고 관리하고 있는 셈이다. 이 코드의 문제는 이미 런타임 시의 의존관계가 코드 속에 다 미리 결정되어 있다는 점이다. 이를 IoC 방식을 써서 UserDao로부터 런타임 의존관계를 드러내는 코드를 제거하고, 제3의 존재에 런타임 의존관계 결정 권환을 위임하면 문제를 해결할 수 있게된다.
    DaoFactory
    public class DaoFactory {
          public UserDao userDao(){
                  return new UserDao(new MyConnectionMaker());
          }
          ...
    }		
    
    UserDao.java
    public class UserDao {
          private ConnectionMaker connectionMaker;
    
          public UserDao(ConnectionMaker conn) {
                  this.connectionMaker = conn;
          }
          ...
    
    UserDao는 ConnectionMaker 인터페이스에만 의존하고 있다. 따라서 ConnectionMaker 인터페이스가 변한다면 그 영향을 UserDao가 직접적으로 받게된다. 하지만 ConnectionMaker 인터페이스를 구현한 클래스, 즉 MyConnectionMaker 등이 다른 것으로 바뀌거나 그 내부에서 사용하는 메소드에 변화가 생겨도 UserDao에 영향을 주지 않는다.
    이렇게 인터페이스에 대해서만 의존관계를 만들어두면 결합도가 낮아진다.
    이처럼 모델이나 코드에서 클래스와 인터페이스를 통해 드러나는 의존관계 말고, 런타임 시에 오브젝트 사이에서 만들어지는 의존관계도 있다. 런타임 의존관계 또는 오브젝트 의존관계인데, 설계 시점의 의존관계가 실체화된 것이라고 볼 수 있다.
    의존관계가 느슨할 경우 UserDao 처럼 런타임 시에 사용할 오브젝트가 어떤 클래스로 만든것인지 미리 알 수가 없다. 개발자나 운영자가 사전에 어떤 클래스의 오브젝트를 쓸지 미리 정해놓을 수는 있지만 그것이 UserDao나 ConnectionMaker 등의 설계와 코드 속에서는 드러나지 않는다는 말이다. 프로그램이 시작되고 UserDao 오브젝트가 만들어지고 나서 런타임 시에 의존관계를 맺는 대상, 즉 실제 사용대상인 오브젝트를 의존 오브젝트(dependent object) 라고 한다.
    의존관계 주입은 이렇게 구체적인 의존 오브젝트와 그것을 사용할 주체, 보통 클라이언트라고 부르는 오브젝트를 런타임시에 연결해주는 작업을 말한다.
    정리하면 의존관계 주입이란 다음과 같은 가지 조건을 충족하는 작업을 말한다.
  • 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.

  • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.

  • 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.

토비의 스프링 3.1

Categories:

Updated:

Leave a comment