8월 30, 2023

Spring 의존성 주입(DI), DI container, 제어의 역전 (IoC) 한번에 이해하기

Spring 프레임워크를 이해하는 것은 스프링프레임워크의 핵심이 의존성 주입 컨테이너라는 사실을 아는 것과, 

다른 모듈들이 작동하기 위하여 필요한 기능을 스프링의 의존성 주입 컨테이너가 제공한다는 사실을 아는 것에서 출발한다.

 

의존성 주입은 어려운 개념은 아니지만 개념을 완전히 이해해야 향후 스프링을 이해하는 것에도 문제가 없다.

 

1. 의존성 주입 (Dependency Injection)

스프링의 가장 기본 뼈대가 되는 의존성 주입이라는 개념에 대해서 알아보자.

 

먼저 "의존성"이란 한 객체가 다른 객체를 사용할 떄 의존성이 있다고 말한다. 

장난감을 주로 팔고 있는 상점을 예시로 생각해보자.

 

public class Store {
	private Toy toy;
}

 

그러면 위와 같이 Store이라는 class 안에 Toy라는 instance를 가지고 있는 구조를 띌 것이다.

 

이럴 떄 우리는 Store가 Toy에 의존성이 있다고 말한다. 즉 Store라는 class가 Toy class를 사용할 때 의존성을 가지는 관계라고 말을 하는 것이다. 

 


 

의존성을 가지고 있을 경우, 우리가 코드를 쓰는 가장 간단한 방법은 아래와 같다.

 

public class Store {
  private Toy toy;
  
  public Store() {
   this.toy= new Toy();
  }
}

 

하지만 위 클래스의 문제는 무엇일까?

 

바로 Store class와 Toy class가 강하게 결합되어 있고 의존성이 높다는 점이다. 만약 Store에서 판매하는 객체의 종류가 달라진다면, Store class 밖에서 무엇인가를 변경하는 것이 아닌, Store class의 생성자 자체를 변경해야 하는 상황이 오게 된다. 

 

이러한 의존성을 제거하기 위해서 Spring에서 자주 쓰는 기법으로

의존성 주입 (Dependency Injection : DI) 라는 개념을 소개하도록 하겠다.

 

의존성 주입이란 객체를 직접 생성하는 것이 아니라, 외부에서 생성한 후 주입시켜 주는 방식으로, 의존성을 낮추어주는 방식을 말한다.

 

위 예시에서는 Store에서 판매하고 있는 상품의 종류가 바뀔 수도 있는 경우를 대비해 

Store안에서 바로 객체 Toy를 생성하는 것이 아니라 외부에서 생성한 후 이를 주입해주어야 한다. 

 

이를 위해서는 Toy의 상위 개념 Product Interface를 하나 생성할 것이다.

 

public interface Product {
 //Toy의 상위 interface
}

public class Toy implements Product {
//Product interface를 구현한 Toy class
}

 

이렇게 상위 Interface를 생성한 뒤, Store class에서 바로 Toy 객체를 생성하는 것이 아니라 

상위 인터페이스로 Product 객체를 생성하는 것이다.

 

public class Store{
 private Product product;
 
 public Store (Product product){
 //생성자를 Toy 객체가 아닌 Product 객체로 받음
 	this.product=product;
 }
}

 

Spring container가 관리하는 객체를 빈 (Bean)이라고 부르며, 이러한 bean들을 관리한다는 의미로 컨테이너를 Bean Factory라고 부른다. 이는 디자인 패턴 중 Factory Design 패턴과도 연관이 있다.

 

따라서 BeanFactory라는 이름의 class를 생성하여 어떻게 의존성이 주입되는 것인지 살펴보겠다. 

 

public class BeanFactory{
	public void create(){
    	Product toy=new Toy(); //Bean 객체 생성
        
        Store store=new Store(toy); //생성자를 통한 의존성 주입
    }
}

위와 같은 방법으로 어플리케이션 실행 시점에 필요한 Bean이 생성되었고 해당 객체를 주입하고자 하는 객체에 주입하는 역할로 BeanFactory, 즉 DI container가 사용되었다. 


2. 제어의 역전 (Inversion of Control)

그리고 제어의 역전(Inversion of Control) 우리가 흔히 부르는 IoC라고 불리는 개념도 의존성 주입이라는 개념으로부터 파생된다.

어떠한 객체를 사용할지에 대한 책임 (ex 위 예시에서 Toy 객체) 은 프레임워크에게 넘기고, 자신은 수동적으로 주입받는 객체를 사용하기 때문이다. 제어가 프레임워크에 넘어갔고, 제어가 역전되었다는 의미로 제어의 역전이라는 용어를 이해하면 어렵지 않게 이해할 수 있을 것이다. 


오늘 포스팅의 예시는 Spring 에서 의존성 주입을 받는 방법 중 한 가지 예시이다. 이외에도 방법이 있으며 오늘 포스팅을 통해서는 대강 흔히 말하는 의존성 주입, 제어의 역전이 어떠한 개념적인 의미를 가지는지를 이해하는 것이 중요하다.