IoC와 DI

초코칩·2024년 3월 17일
0

spring

목록 보기
4/10
post-thumbnail

IoC와 DI

IoC와 DI의 주 목적은 컴포넌트의 의존성을 제공하고 이러한 의존성을 라이프사이클 전반에 걸쳐 관리하는보다 간편한 메커니즘을 제공하는 것이다. 의존성이 필요한 컴포넌트를 의존 객체 (dependent object)라 하고 IoC에서는 대상(target)이라고 한다. 일반적으로 IoC는 의존성 주입(DI)과 의존성 룩업(Dependency Lookup, DL)의 두 가지 하위분류로 나눌 수 있다. 이들 하위분류는 다시 구체적인 IoC 서비스 구현체로 나뉜다. 이러한 정의를 통해 알 수 있는 사실은, DI에 관련된 설명은 항상 IoC에 관련된 설명이지만 IoC에 관한 설명이 항상 DI에 관한 설명인 것은 아니라는 점이다.

IoC의 종류

의존성 룩업 방식의 IoC에서는 컴포넌트 스스로 의존성의 참조를 가져와야 한다. 반면에 의존성 주입 방식의 IoC에 서는 IoC 컨테이너가 컴포넌트에 의존성을 주입한다.

의존성 룩업은 의존성 풀(dependency pull)문맥에 따른 의존성 룩업(Contextualized Dependency Lookup, CDL)이라는 두 가지 방식으로 나뉜다. 의존성 주입도 생성자(constructor) 의존성 주입수정자(setter) 의존성 주입의 두 가지 방식으로 나뉜다.

의존성 풀

자바 개발자에게 의존성 풀(dependency pull)은 가장 익숙한 IoC 방식이다. 의존성 풀에서는 필요에 따라 레지스트리에서 가져오게 된다. EJB(2.X 이하 버전)에서는 이를 사용했다.

문맥에 따른 의존성 룩업

문맥에 따른 의존성 룩업(CDL)은 의존성 풀과 유사하지만 차이점이 있다. 우선 CDL의 의존성 풀처럼 특정 중앙 레지스트리에서의 의존성을 가져오는 것이 아니라 자원을 관리하는 컨테이너에서 의존성을 가져온다. 또한 CDL은 늘 수행되는 것이 아닌 몇 가지 정해진 시점에 수행된다.

CDL은 컴포턴트가 다음 코드와 비슷한 인터페이스를 구현하는 방식으로 동작한다.

public interface ManagedComponent {
	void performLookup(Container container);
}

컴포넌트는 이 인터페이스를 구현해 의존 관계를 얻으려는 컨테이너에 신호를 보낸다. 일반적으로 컨테이너는 톰캣(애플리케이션 서버)이나 스프링(애플리케이션 프레임워크)과 같은 곳에서 제공한다.

public interface Container {
	Object getDependency(String key);
}

컨테이너가 컴포넌트에 의존성을 전달할 준비가 되면 컨테이너는 차례대로 performLookup() 메서드를 호출한다.

생성자 의존성 주입

생성자 의존성 주입은 컴포넌트의 생성자를 이용해서 해당 컴포넌트가 필요로하는 의존성을 제공하는 방식이다. 어떤 컴포넌트가 의존성을 인수로 가져오도록 생성자 또는 여러 생성자를 선언한다면 IoC 컨테이너는 해당 컴포넌트를 초기화할 때 컴포넌트에 필요한 의존성을 전달한다.

Setter 의존성 주입

Setter 의존성 주입 방식에서 IoC 컨테이너는 자바빈 방식의 Setter 메서드를 이용해 컴포넌트의 의존성을 주입한다. 컴포넌트의 setter는 IoC 컨테이너가 관리할 수 있도록 의존성을 노출한다.

Setter 주입을 사용할 때 명확한 것은 의존성 없이도 객체를 생성할 수 있으며 해당 수정자를 호출해 의존성을 나중에 제공할 수 있다는 것이다. 컨테이너 내에서 setDependency() 메서드가 필요로 하는 의존성은 자바빈 명명 규칙에 따라 dependency를 참조한다. 실제로 수정자 의존성 주입은 가장 널리 사용되며 구현하기 가장 간단한 의존성 주입 메커니즘 중 하나다.

의존성 주입 vs 의존성 룩업

의존성 주입과 의존성 룩업 중에 어떤 것을 선택할지 결정하는 일은 일반적으로 어렵지 않다. 많은 경우에 현재 사용하는 컨테이너에 따라 IoC의 방식이 정해진다. 스프링에서는 초기 빈 룩업을 제외하면 컴포넌트와 의존성은 항상 의존성 주입 방식의 IoC를 이용해 연결된다.

스프링을 사용할 때는 명시적인 의존성 룩업을 수행하지 않고도 EJB 자원에 접근할 수 있습니다. 스프링 이 의존성 룩업과 의존성 주입 방식의 시스템 사이에서 어댑터 역할을 할 수 있으므로 의존성 주입을 사용해 모든 리 소스를 관리할 수 있습니다.

이때 정말 궁금한 것은 의존성 주입과 의존성 룩업 중에서 선택해야만 한다면 어떤 것을 선택해야 하는지다. 이 질문에 대한 정답은 당연히 의존성 주입입니다.

의존성 주입의 장점

  • 변화에 대한 코드의 영향도: 의존성 주입이 컴포넌트에 어떠한 코드 변화도 일으키지 않지만, 의존성 풀의 코드는 레지스트리에 대한 참조를 얻어와서 의존성을 얻어야 한다.

  • 의존성 책임: CDL을 사용하면 클래스는 특정 인터페이스를 구현하고 모든 의존성을 직접 가져와야 하지만, 의존성 주입을 사용하면 대부분의 클래스가 해야 할 일은 생성자나 수정자로 의존성이 주입될 수 있게 하는 것뿐이다.

  • IoC 컨테이너와의 결합도: 의존성 주입을 이용하면 사용자 클래스는 협력 객체(collaborator)를 의존 객체에게 제공하는 IoC 컨테이너와 완전히 분리돼 자유롭게 사용될 수 있는 반면에, 룩업을 이용하면 사용자 클래스는 컨테이너에 의해 정의된 클래스와 인터페이스에 항상 의존하게 된다.

  • 테스트의 용이성: 룩업의 단점은 클래스를 컨테이너와 분리시킨 채 테스트하기가 어렵다는 점이다. 주입을 이용하면 적절한 생성자나 수정자를 사용해 사용자가 직접 테스트용 의존성을 쉽게 제공할 수도 있으므로 컴포넌트를 쉽게 테스트할 수 있다.

  • 복잡성: 의존성 룩업 방식은 의존성 주입 방식보다 훨씬 더 복잡할 수도 있습니다. 의존성 관리를 애플리케이션에서 직접 처리하게 함으로써 불필요하게 복잡해질 이유가 전혀 없다.

생성자 주입 vs Setter 주입

생성자 주입의 장점

생성자 주입은 컴포넌트 사용 전에 해당 컴포넌트의 의존성을 반드시 갖고 있어야 할 때 매우 유용하다.

  • 수정자 주입을 사용할 때도 모든 의존성을 확인할 수 있는 메커니즘을 제공하긴 하지만, 생성자 주입을 사용하면 컨테이너가 의존성 점검 메커니즘을 제공하는지와 상관없이 의존성에 대한 요구사항을 지정할 수 있다.
  • 생성자 주입을 사용하면 빈 객체를 불변 객체(immutable object)로 사용할 수 있다.

Setter 주입의 장점

수정자 주입은 다양한 상황에서 유용합니다.

  • 컴포넌트가 의존성을 컨테이너로 노출하지만, 기본 의존성을 제공할 때는 일반적으로 수정자 주입이 의존성 주입에 가장 좋은 방법이다.

  • 인터페이스에서 모든 의존성을 선언 할 수 있다는 것이다. 스프링을 비롯한 최근의 모든 IoC 컨테이너는 사용자의 비즈니스 인터페이스를 사용해 사용자의 컴포넌트를 동작시키면서 구현 클래스의 의존성도 제공한다.

  • 수정자 주입을 사용하면 부모 컴포넌트의 인스턴스를 새로 생성하지 않고도 즉시 의존성을 다른 구현체로 교체할 수 있다. 이는 스프링의 JMX 지원 기능 덕분에 가능하다.

  • 주입 메커니즘 중 가장 자유롭게 사용할 수 있다는 것이다. 일반적으로 사용 사례에 따라 주입 방식을 선택해야 하는데, 수정자 주입을 사용하면 새로운 객체를 생성하지 않아도 의존성을 교체할 수 있고, 명시적으로 객체를 주입하지 않더라도 적절한 기본 값을 선택하게 할 수 있다.

생성자 주입은 컴포넌트에 의존성 주입을 보장하거나 불변 객체를 설계하는 경우에 좋은 선택이다. 생성자 주입이 컴포넌트에 필요한 모든 의존성 제공을 보장하지만, 대부분의 컨테이너도 의존성 제공을 보장하는 메커니즘을 제공하며 이때는 사용자 코드를 프레임워크에 결합해야 하는 부담이 따를 수 있다는 점을 명심해야 한다.

Spring IoC

앞서 언급했듯이 제어 역전(Inversion of Control, loC)은 스프링이 하는 일 중에서 큰 부분을 차지하며, 스프링 구현의 핵심은 의존성 주입이다. 스프링은 의존 객체에 협력 객체를 자동으로 제공하기 위해 의존성 주입을 이용한다. 스프링 기반의 애플리케이션에서도 의존 객체가 협력 객체를 룩업해 오는 것보다, 협력 객체를 의존 객체에 전달하는 의존성 주입을 사용하는 것이 항상 바람직하다. 아래 그림은 스프링의 의존성 주입 메커니즘은 스프링의 의존성 주입 메커니즘을 보여준다.

의존성 주입은 협력 객체와 의존 객체를 서로 연결할 때 선호하는 메커니즘이지만, 의존 객체에 접근하려면 의존성 룩업이 필요하다. 스프링은 다양한 환경에서 의존성 주입만으로 모든 애플리케이션 컴포넌트을 자동으로 연결할 수 없으며, 이런 경우에 의존성 룩업을 이용해 초기 컴포넌트에 접근해야 한다.

예를 들어 독립형 자바 애플리케 이션에서 스프링을 사용하도록 프로그래밍하려면 main() 메서드에서 스프링의 컨테이너를 부트스트랩하고 의존성을(ApplicationContext 인터페이스를 이용) 가져와야 한다. 하지만 스프링의 MVC 기능을 사용해 웹 애플리케이션을 개발할 때는 스프링이 애플리케이션 전체를 자동으로 연결시키므로 의존성 룩업이 필요하지 않다. 스프링으로 의존성 주입을 할 수 있다면 그렇게 해야 하고, 그렇지 않은 경우에는 의존성 룩업 기능을 사용하면 된다.

Spring IoC 컨테이너의 특징 중 하나는 스프링 IoC 컨테이너가 자체 의존성 주입 컨테이너와 외부 의존성 컨테이너 룩업 컨테이너 사이의 어뎁터 역할을 한다는 점이다.

스프링은 생성자 주입과 수정자 주입 방식 모두를 지원하며, 표준 IoC 기능들을 보완해 사용자 편 의성을 높여주는 유용한 추가 기능을 제공한다.

profile
초코칩처럼 달콤한 코드를 짜자

0개의 댓글