[Spring] IoC/DI 개념과 이해

enjoy89·2023년 1월 17일
3
post-thumbnail
post-custom-banner

스프링 프레임워크의 3가지 핵심 프로그래밍 모델 중 하나인 IoC/DI에 대해 알아보도록 하자.

IoC/DI 개념

IoC(Inversion of Control) - 제어의 역전

  • 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 의미한다.
  • 스프링은 오브젝트(빈)의 생성과 의존 관계 설정, 사용, 제거 등의 작업을 애플리케이션 코드 대신 스프링 컨테이너가 담당한다.
  • 이를 스프링 컨테이너가 코드 대신 오브젝트에 대한 제어권을 갖고 있다고 해서 IoC라고 부른다.
  • 따라서, 스프링 컨테이너를 IoC 컨테이너라고도 부른다.

DI(Dependency Injection) - 의존성 주입

  • 스프링 프레임워크에서 제공하는 의존 관계 주입 기능으로, 객체를 직접 생성하는 것이 아니라 외부에서 생산한 후 주입 시켜주는 방식이다.
  • 의존 관계는 객체 사이의 new라는 키워드를 사용하여 생성할 수 있다.
  • 즉, 외부에서 두 객체 간의 관계를 결정해주는 것으로 객체 사이의 결합도를 낮추고 유연성을 확보할 수 있다.

IoC/DI의 필요성

  • 예를 들어 자동차가 타이어를 생산하여 사용한다고 해보자.
  • 타이어는 KoreaTire와 AmericaTire 두 가지의 종류가 있다.

Tire.java

public interface Tire {
    String getBrand();
}

KoreaTire.java

public class KoreaTire implements Tire {

    public String getBrand() {
        return "한국 타이어";
    }
}

AmericaTire.java


public class AmericaTrie implements Tire {

    public String getBrand() {
        return "미국 타이어";
    }
}
  • 자동차 객체는 타이어를 생산(주입)할 수 있는데
  • 이때 Car 클래스의 생성자를 보면 Car 클래스와 KoreaTire 클래스는 직접적으로 강하게 결합되어 있는 것을 확인할 수 있다.

Car.java

public class Car {
    Tire tire;

    // 생성자
		public Car() {
        tire = new KoreaTire();  // new 키워드를 통해 의존 관계 생성
    }

    public String getTrieBrand() {
        return "정착된 타이어: " + tire.getBrand();
    }
}
  • 운전자는 자동차를 생산(주입)할 수 있다.
  • 하지만 운전자는 자동차의 타이어를 따로 지정할 수는 없다. (문제발생의 원인)

Driver.java


public class Driver {
    public static void main(String[] args) {
        Car car = new Car();
        System.out.println(car.getTrieBrand());
    }
}
  • 만약 이때 운전자가 자동차의 타이어를 KoreaTire가 아닌 AmericaTire로 변경하여 장착하고 싶다면 어떻게 해야할까?
  • 자동차의 타이어는 Car 클래스에서 직접 생산(주입)했으니, Car 클래스의 생산자 코드의 변경이 필요할 것이다.
public class Car {
    Tire tire;

    public Car() {
        tire = new AmericaTire();  // 변경 
    }
    ...
}
  • 이처럼 운전자가 자동차의 타이어를 변경하기 위해서는 Car 클래스의 생산자 코드까지 가서 수정을 해야 하는데, 자동차마다 다른 타이어를 장착하기 위해서 여러 개의 Car 클래스가 파생되는 것은 매우 효율적이지 못하다. 즉, 유연성이 떨어진다고 한다.
  • 이러한 유연성의 문제점을 해결하기 위해 Car 클래스를 다음과 같이 수정해보자.

수정된 Car.java

public class Car {
    Tire tire;

		// 변경
    public Car(Tire tire) {
        this.tire = tire;   
    }

    public String getTrieBrand() {
        return "정착된 타이어: " + tire.getBrand();
    }
}
  • 이처럼 Car 클래스의 생성자를 통해 Car 클래스에서 구체 타이어 클래스에 의존하는 것을 막을 수 있다.
  • 즉, Car 클래스는 직접 타이어를 생산하는 것이 아닌 생성자로 주입을 받는다. 이처럼 의존성을 역전시켜 Car 클래스가 직접 제어권을 갖지 않는 것을 IoC(제어의 역전)라고 한다.

수정된 Driver.java

public class Driver {
    public static void main(String[] args) {
				// 변경
        Tire tire = new AmericaTire();
        Car car = new Car(tire);
        System.out.println(car.getTrieBrand());
    }
}
  • 이제는 운전자가 자동차를 생산할 때 쉽게 원하는 타이어를 골라 장착할 수 있게 되었다. 또한, 나중에 타이어를 교체하고 싶을 때 Driver 클래스 안에서만 타이어의 종류를 바꿔 자동차를 생산하면 된다.
  • Car 클래스는 외부 클래스인 Driver 클래스에서 생성된 타이어 객체를 수동적으로 주입 받아 사용하는 것을 확인할 수 있다. 이처럼 의존성을 외부 클래스인 Driver 클래스에서 주입시켜 주는 것을 DI(의존성 주입)라고 한다.
  • 이와 같은 예시를 통해 IoC/DI 방식을 이용하여 유연하고 확장성이 뛰어난 프로그래밍을 만들 수 있다는 것을 알 수 있었다.

정리

  • 스프링은 유연하고 확장성이 뛰어난 코드를 작성하도록 도와주는 객체지향 설계 원칙과 디자인 패턴의 핵심 원리를 담고 있는 IoC/DI라는 개념을 프레임워크의 근간으로 삼는다.
  • 그렇기 때문에 스프링에서 개발되는 코드들은 IoC/DI 방식을 따라 작성되어야 객체의 책임을 분리하고 협력하도록 하여야 한다.
  • 스프링에서는 Spring 컨테이너, IoC 컨테이너라는 개념을 사용한다. 컨테이너는 보통 인스턴스의 생명주기를 관리하며, 생성된 인스턴스들에게 추가적인 기능을 제공하도록 하는 것이라 할 수 있다.
  • 다시 말해, 컨테이너란 당신이 작성한 코드의 처리과정을 위임받은 독립적인 존재라고 생각하면 된다. 컨테이너는 적절한 설정만 되어있다면 누구의 도움 없이도 프로그래머가 작성한 코드를 스스로 참조한 뒤 알아서 객체의 생성과 소멸을 컨트롤해준다.
  • 즉, 프로그램의 제어의 흐름을 프로그래머가 컨트롤 하는 것이 아니라 스프링에게 맡겨 작업을 처리하게 된다.
  • 스프링 컨테이너는 스프링 프레임워크의 핵심부에 위치하며, 종속객체 주입을 이용하여 애플리케이션을 구성하는 컴포넌트들을 관리한다. 이때 스프링 컨테이너에서 생성되는 객체를 Bean이라고 한다. 이는 기본적으로 싱글톤(singleton)으로 관리한다.

Reference

https://steady-coding.tistory.com/600

https://leveloper.tistory.com/33

https://mangkyu.tistory.com/150

김종민, [스프링 입문을 위한 자바 객체 지향의 원리와 이해]

profile
Backend Developer 💻 😺
post-custom-banner

0개의 댓글