1. 개요

이 예제에서는 IoC(Inversion of Control) 및 DI(Dependency Injection)의 개념을 소개하고 이러한 개념이 Spring 프레임워크에서 어떻게 구현되는지 살펴봅니다.

2. 제어 역전이란 무엇입니까?

제어 역전은 개체 또는 프로그램의 일부에 대한 제어를 컨테이너 또는 프레임워크로 이전하는 소프트웨어 엔지니어링의 원칙입니다. 우리는 객체 지향 프로그래밍의 맥락에서 그것을 가장 자주 사용합니다.

사용자 지정 코드가 라이브러리를 호출하는 기존 프로그래밍과 달리 IoC는 프레임워크가 프로그램의 흐름을 제어하고 사용자 지정 코드를 호출할 수 있도록 합니다. 이를 가능하게 하기 위해 프레임워크는 추가 동작이 내장된 추상화를 사용합니다. 자체 동작을 추가하려면 프레임워크의 클래스를 확장하거나 자체 클래스를 플러그인해야 합니다.

이 아키텍처의 장점은 다음과 같습니다.

  • 작업 실행을 구현과 분리
  • 서로 다른 구현 간에 더 쉽게 전환할 수 있도록 합니다.
  • 프로그램의 더 큰 모듈성
  • 구성 요소를 격리하거나 의존성을 조롱하고 구성 요소가 계약을 통해 통신할 수 있도록 하여 프로그램 테스트를 더욱 용이하게 합니다.

전략 디자인 패턴, 서비스 로케이터 패턴, 팩토리 패턴, 의존성 주입(DI)과 같은 다양한 메커니즘을 통해 제어 역전을 달성할 수 있습니다.

다음은 DI에 대해 알아보겠습니다.

3. 의존성 주입이란?

의존성 주입은 IoC를 구현하는 데 사용할 수 있는 패턴입니다. 여기서 반전되는 컨트롤은 개체의 의존성을 설정합니다.

개체를 다른 개체와 연결하거나 개체를 다른 개체에 "주입"하는 것은 개체 자체가 아니라 어셈블러에 의해 수행됩니다.

기존 프로그래밍에서 객체 의존성을 만드는 방법은 다음과 같습니다.

public class Store {
    private Item item;
 
    public Store() {
        item = new ItemImpl1();    
    }
}

위의 예에서는 Store 클래스 자체 내에서 Item 인터페이스 의 구현을 인스턴스화해야 합니다.

DI를 사용하여 원하는 항목 의 구현을 지정하지 않고 예제를 다시 작성할 수 있습니다 .

public class Store {
    private Item item;
    public Store(Item item) {
        this.item = item;
    }
}

다음 섹션에서는 메타데이터를 통해 Item 구현을 제공하는 방법을 살펴보겠습니다 .

IoC와 DI는 둘 다 단순한 개념이지만 시스템을 구성하는 방식에 깊은 의미가 있으므로 완전히 이해할 가치가 있습니다.

4. 스프링 IoC 컨테이너

IoC 컨테이너는 IoC를 구현하는 프레임워크의 일반적인 특성입니다.

Spring 프레임워크에서 인터페이스  ApplicationContext 는 IoC 컨테이너를 나타냅니다. Spring 컨테이너는 Bean 으로 알려진 객체를 인스턴스화, 구성 및 조합 하고 수명 주기를 관리하는 역할을 합니다.

Spring 프레임워크는 독립형 애플리케이션을 위한 ClassPathXmlApplicationContextFileSystemXmlApplicationContext 와 웹 애플리케이션을 위한 WebApplicationContext 와 같은 ApplicationContext 인터페이스 의 여러 구현을 제공합니다.

빈을 어셈블하기 위해 컨테이너는 XML 구성 또는 어노테이션의 형태일 수 있는 구성 메타데이터를 사용합니다.

다음은 컨테이너를 수동으로 인스턴스화하는 한 가지 방법입니다.

ApplicationContext context
  = new ClassPathXmlApplicationContext("applicationContext.xml");

위의 예에서 item 속성 을 설정하기 위해 메타데이터를 사용할 수 있습니다. 그런 다음 컨테이너는 이 메타데이터를 읽고 런타임에 빈을 어셈블하는 데 사용합니다.

Spring의 의존성 주입은 생성자, 설정자 또는 필드를 통해 수행할 수 있습니다.

5. 생성자 기반 의존성 주입

생성자 기반 의존성 주입경우 컨테이너는 각각 우리가 설정하려는 의존성을 나타내는 인수를 사용하여 생성자를 호출합니다.

Spring은 기본적으로 유형에 따라 각 인수를 해결하고 그 뒤에 속성 이름과 명확성을 위한 색인이 옵니다. 어노테이션을 사용하여 빈의 구성과 의존성을 살펴보겠습니다.

@Configuration
public class AppConfig {

    @Bean
    public Item item1() {
        return new ItemImpl1();
    }

    @Bean
    public Store store() {
        return new Store(item1());
    }
}

@Configuration 어노테이션은 클래스가 빈 정의의 소스임을 나타냅니다 . 여러 구성 클래스에 추가할 수도 있습니다.

Bean을 정의하기 위해 메소드에 @Bean 어노테이션을 사용합니다 . 사용자 정의 이름을 지정하지 않으면 빈 이름은 기본적으로 메소드 이름이 됩니다.

기본 싱글톤 범위 를 가진 빈의 경우 Spring은 먼저 빈의 캐시된 인스턴스가 이미 존재하는지 확인하고 존재하지 않는 경우에만 새 인스턴스를 생성합니다. 프로토타입 범위 를 사용하는 경우 컨테이너는 각 메서드 호출에 대해 새 빈 인스턴스를 반환합니다.

빈의 구성을 만드는 또 다른 방법은 XML 구성을 사용하는 것입니다.

<bean id="item1" class="org.baeldung.store.ItemImpl1" /> 
<bean id="store" class="org.baeldung.store.Store"> 
    <constructor-arg type="ItemImpl1" index="0" name="item" ref="item1" /> 
</bean>

6. 세터 기반 의존성 주입

setter 기반 DI의 경우 컨테이너는 인수가 없는 생성자 또는 인수가 없는 정적 팩토리 메서드를 호출하여 빈을 인스턴스화한 후 클래스의 setter 메서드를 호출합니다. 어노테이션을 사용하여 이 구성을 생성해 보겠습니다.

@Bean
public Store store() {
    Store store = new Store();
    store.setItem(item1());
    return store;
}

동일한 빈 구성에 대해 XML을 사용할 수도 있습니다.

<bean id="store" class="org.baeldung.store.Store">
    <property name="item" ref="item1" />
</bean>

동일한 빈에 대해 생성자 기반 및 설정자 기반 주입 유형을 결합할 수 있습니다. Spring 문서에서는 필수 의존성에 대해서는 생성자 기반 주입을 사용하고 선택적 의존성에 대해서는 setter 기반 주입을 사용할 것을 권장합니다.

7. 필드 기반 의존성 주입

필드 기반 DI의 경우 @Autowired 어노테이션 으로 표시하여 의존성을 주입할 수 있습니다 .

public class Store {
    @Autowired
    private Item item; 
}

Store 객체를 생성하는 동안 Item 빈 을 주입할 생성자 또는 설정자 메서드가 없으면 컨테이너는 리플렉션을 사용하여 ItemStore 에 주입 합니다.

XML 구성 을 사용하여 이를 달성할 수도 있습니다 .

이 접근 방식은 더 간단하고 깔끔해 보일 수 있지만 다음과 같은 몇 가지 단점이 있으므로 사용하지 않는 것이 좋습니다.

  • 이 메서드는 리플렉션을 사용하여 의존성을 주입하며, 이는 생성자 기반 또는 설정자 기반 주입보다 비용이 많이 듭니다.
  • 이 접근 방식을 사용하여 여러 의존성을 계속 추가하는 것은 정말 쉽습니다. 생성자 주입을 사용하는 경우 인수가 여러 개 있으면 클래스가 한 가지 이상을 수행한다고 생각하게 되어 단일 책임 원칙을 위반할 수 있습니다.

@Autowired 어노테이션 에 대한 자세한 내용 은 Wiring In Spring 기사에서 찾을 수 있습니다.

8. 의존성 자동 연결

배선 을 통해 Spring 컨테이너는 정의된 빈을 검사하여 협업하는 빈 간의 의존성을 자동으로 해결할 수 있습니다.

XML 구성을 사용하여 빈을 자동 연결하는 네 가지 모드가 있습니다.

  • no : 기본값 – 이것은 bean에 대해 autowiring이 사용되지 않고 의존성을 명시적으로 명명해야 함을 의미합니다.
  • byName : 속성의 이름을 기반으로 autowiring이 이루어지므로 Spring은 설정해야 할 속성과 이름이 같은 빈을 찾습니다.
  • byType : 속성 유형에만 기반한 byName 자동 연결과 유사합니다이것은 Spring이 설정할 동일한 유형의 속성을 가진 빈을 찾을 것임을 의미합니다. 해당 유형의 Bean이 두 개 이상 있으면 프레임워크에서 예외가 발생합니다.
  • constructor : autowiring은 생성자 인수를 기반으로 수행됩니다. 즉, Spring은 생성자 인수와 동일한 유형의 빈을 찾습니다.

예를 들어 위에서 정의한 item1 빈을 유형별로 store 빈에 자동 연결해 보겠습니다.

@Bean(autowire = Autowire.BY_TYPE)
public class Store {
    
    private Item item;

    public setItem(Item item){
        this.item = item;    
    }
}

유형별 자동 연결을 위해 @Autowired 어노테이션을 사용하여 빈을 주입할 수도 있습니다 .

public class Store {
    
    @Autowired
    private Item item;
}

동일한 유형의 빈이 두 개 이상 있는 경우 @Qualifier 어노테이션을 사용하여 이름으로 빈을 참조할 수 있습니다.

public class Store {
    
    @Autowired
    @Qualifier("item1")
    private Item item;
}

이제 XML 구성을 통해 유형별로 빈을 자동 연결해 보겠습니다.

<bean id="store" class="org.baeldung.store.Store" autowire="byType"> </bean>

다음으로 XML을 통해 이름별로 store bean 의 item 속성에 item 이라는 이름의 bean을 삽입해 보겠습니다.

<bean id="item" class="org.baeldung.store.ItemImpl1" />

<bean id="store" class="org.baeldung.store.Store" autowire="byName">
</bean>

생성자 인수 또는 설정자를 통해 명시적으로 의존성을 정의하여 자동 연결을 재정의할 수도 있습니다.

9. 지연 초기화 빈

기본적으로 컨테이너는 초기화 중에 모든 싱글톤 빈을 만들고 구성합니다. 이를 피하기 위해 빈 설정에서 값이 true 인 lazy-init 속성을 사용할 수 있습니다.

<bean id="item1" class="org.baeldung.store.ItemImpl1" lazy-init="true" />

결과적으로 item1 빈은 시작할 때가 아니라 처음 요청될 때만 초기화됩니다. 이것의 장점은 더 빠른 초기화 시간이지만, 빈이 요청될 때까지 구성 오류를 발견하지 못한다는 단점이 있습니다. 이는 애플리케이션이 이미 실행된 후 몇 시간 또는 며칠이 될 수 있습니다.

10. 결론

이 글에서 우리는 Inversion of Control과 Dependency Injection의 개념을 제시하고 이를 Spring 프레임워크에서 예시했다.

이러한 개념에 대한 자세한 내용은 Martin Fowler의 기사에서 확인할 수 있습니다.

또한 Spring Framework Reference Documentation 에서 IoC 및 DI의 Spring 구현에 대해 배울 수 있습니다 .

Generic footer banner