본문 바로가기
Spring&SpringBoot/<토비의 스프링 3.1 Vol1.1>, 이일민

DI와 IoC의 차이는 무엇이고 스프링 컨테이너는 무엇을 지원하는가?

by 민휘 2023. 3. 2.

IoC와 DI의 관계, 그리고 스프링 애플리케이션 컨텍스트

IoC는 소프트웨어에서 자주 발견되는 일반적인 개념이다. 제어권을 이전해서 기존의 제어관계가 역전되는 것을 IoC라고 부른다. IoC는 프레임워크, 컨테이너에서 동작하는 서버 기술, 디자인 패턴 등 객체지향적인 설계에서 사용된다. 프레임워크는 애플리케이션 코드 작성 방법을 정하는 권한을 개발자에서 프레임워크로 넘긴다. 컨테이너는 컴포넌트의 실행 권한을 엔트리포인트에서 컨테이너로 넘긴다. 템플릿 메소드 패턴은 메소드의 실행 권한을 상위 객체에게 넘긴다. 코드의 관심사 분리 관점에서 객체의 생성 권한을 외부 객체에게 넘기는 것이 된다.

스프링 IoC 컨테이너는 특히 객체의 생성 권한의 이전을 통해 런타임 의존관계를 동적으로 만드는데 집중한다. 이를 DI(의존성 주입)이라고 한다. 결국 DI는 IoC의 다양한 형태 중 하나이다. 설정 정보 클래스를 애플리케이션 컨텍스트에게 넘겨주면 스프링이 알아서 객체를 생성하고 관리한다. 클라이언트의 요청이 들어오면 관리하고 있던 객체(스프링 빈)을 반환해 주입해준다.

 

의존성 주입은 관심사 분리가 발생했을 때, 분리된 객체의 의존관계를 만들어주는 것이다. 이를 의존성 해결이라고 한다.(의존성 주입은 의존성 해결 방법 중 하나이다) 생성과 사용이라는 서로 다른 관심사를 분리하고 나면 어떤 객체는 사용의 책임만을 가지고, 다른 객체는 생성 책임만을 가진다. 사용 책임을 가지는 객체는 사용할 객체를 외부에서 해결해서 받아야 한다. 이때 사용 책임 객체는 생성 책임 객체의 내부를 알 수 없고, 생성 책임 객체가 메시지의 응답으로 반환해주는 객체만을 사용할 수 있다. 따라서 객체를 어떻게 생성할 것인지, 즉 어떤 구현을 선택해서 관계를 구성할 것인지는 생성 책임 객체에게 전적으로 맡길 수 있다. 이때 사용 객체가 생성되는 객체의 인터페이스를 참조하고 있다면 런타임 시점의 의존관계를 동적으로 만들 수 있다.

 

의존성 주입과 OCP

의존성 주입은 구체적인 오브젝트와 클라이언트 오브젝트를 런타임 시에 연결해주는 작업을 말한다. 이때 클라이언트는 사용할 오브젝트의 인터페이스에 의존한다. 따라서 컴파일 의존성은 클라이언트와 인터페이스가 결합되어 있지만, 런타임 의존성은 의존성 주입에 의해 클라이언트와 구체적인 오브젝트가 연결된다. 런타임 중에 의존성이 변경될 수 있으므로 유연한 객체 협력을 만들 수 있다.

 

컴파일 의존성은 코드에 의해 결정된다. 클라이언트와 인터페이스가 의존하고 있으므로 컴파일 의존성은 이 둘이 연결된 상태이다. 런타임 의존성에서는 인터페이스를 이해할 수 있는 구체적인 오브젝트라면 클라이언트와 결합될 수 있다. 그렇긴 때문에 기존 시스템에 새로운 오브젝트를 추가하고 싶다면, 오브젝트가 인터페이스를 구현하도록 하고 설정 정보에 구체 오브젝트를 사용하도록 수정하기만 하면 된다. 의존성 주입을 사용하면 코드는 변하지 않되 기능은 확장할 수 있는 OCP를 지킬 수 있다.

 

실행 중에 의존성 주입에 의해 다른 구체적인 오브젝트로 변경될 수 있다. 우리가 작성했던 스프링 컨텍스트에 넘겨주는 설정 파일이 런타임 의존성을 결정하는 책임을 담당한다.

 

의존성 주입과 의존성 검색의 차이

스프링이 지원하는 IoC의 대표적인 방법은 의존성 주입이다. 하지만 의존성 검색으로도 IoC가 가능하다. 다음과 같이 애플리케이션 컨텍스트에 빈 이름으로 빈을 요청하면 IoC 컨테이너에서 생성된 구현을 인터페이스로 받아올 수 있다.

public UserDao() {
	ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
	this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
}

그렇다면 의존성 주입과 의존성 검색은 무엇이 다를까? 검색 방식에서 검색을 하는 객체(UserDao)는 스프링의 빈으로 등록될 필요가 없다. 검색하고 싶은 객체(ConnectionMaker)만 스프링 빈으로 등록되어있으면 된다. 하지만 주입은 주입을 요청한 객체(UserDao)도 반드시 스프링의 빈이어야 한다. 검색은 단순한 읽기 작업이기 때문에 검색하는 객체가 빈이 아니어도 된다. new 연산자로 생성해도 상관없다. 하지만 주입은 참조값의 변경이 발생하는 쓰기 작업이다. 검색하는 객체의 런타임 의존성이 달라진다. 따라서 검색하는 객체 역시 컨텍스트에 의해 관리되어야 한다.