Spring을 사용한 Inversion of Control 및 종속성 주입 소개

1. 개요

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

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

Inversion of Control은 프로그램의 개체 또는 일부에 대한 제어가 컨테이너 또는 프레임 워크로 전송되는 소프트웨어 엔지니어링의 원칙입니다. 객체 지향 프로그래밍의 맥락에서 가장 자주 사용됩니다.

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

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

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

Inversion of Control은 전략 설계 패턴, 서비스 로케이터 패턴, 팩토리 패턴 및 DI (Dependency Injection)와 같은 다양한 메커니즘을 통해 달성 할 수 있습니다.

다음에 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. Spring IoC 컨테이너

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

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

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

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

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

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

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

Spring의 Dependency Injection은 생성자, setter 또는 필드를 통해 수행 할 수 있습니다.

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

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

Spring은 각 인수를 주로 유형별로 해결 한 다음 애트리뷰트 이름과 명확성을 위해 인덱스를 따릅니다. 주석을 사용하여 Bean의 구성 및 종속성을 살펴 보겠습니다.

@Configuration public class AppConfig { @Bean public Item item1() { return new ItemImpl1(); } @Bean public Store store() { return new Store(item1()); } }

@Configuration의 주석은 클래스는 bean 정의의 원천임을 나타냅니다. 또한 여러 구성 클래스에 추가 할 수 있습니다.

@Bean 어노테이션은 빈을 정의하는 방법에 사용된다. 사용자 정의 이름을 지정하지 않으면 Bean 이름이 메소드 이름으로 기본 설정됩니다.

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

Bean의 구성을 작성하는 또 다른 방법은 XML 구성을 사용하는 것입니다.

6. 세터 기반 의존성 주입

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

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

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

Constructor-based and setter-based types of injection can be combined for the same bean. The Spring documentation recommends using constructor-based injection for mandatory dependencies, and setter-based injection for optional ones.

7. Field-Based Dependency Injection

In case of Field-Based DI, we can inject the dependencies by marking them with an @Autowired annotation:

public class Store { @Autowired private Item item; }

While constructing the Store object, if there's no constructor or setter method to inject the Item bean, the container will use reflection to inject Item into Store.

We can also achieve this using XML configuration.

This approach might look simpler and cleaner but is not recommended to use because it has a few drawbacks such as:

  • This method uses reflection to inject the dependencies, which is costlier than constructor-based or setter-based injection
  • It's really easy to keep adding multiple dependencies using this approach. If you were using constructor injection having multiple arguments would have made us think that the class does more than one thing which can violate the Single Responsibility Principle.

More information on @Autowired annotation can be found in Wiring In Spring article.

8. Autowiring Dependencies

Wiring allows the Spring container to automatically resolve dependencies between collaborating beans by inspecting the beans that have been defined.

There are four modes of autowiring a bean using an XML configuration:

  • no: the default value – this means no autowiring is used for the bean and we have to explicitly name the dependencies
  • byName: autowiring is done based on the name of the property, therefore Spring will look for a bean with the same name as the property that needs to be set
  • byType: similar to the byName autowiring, only based on the type of the property. This means Spring will look for a bean with the same type of the property to set. If there's more than one bean of that type, the framework throws an exception.
  • constructor: autowiring is done based on constructor arguments, meaning Spring will look for beans with the same type as the constructor arguments

For example, let's autowire the item1 bean defined above by type into the store bean:

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

We can also inject beans using the @Autowired annotation for autowiring by type:

public class Store { @Autowired private Item item; }

If there's more than one bean of the same type, we can use the @Qualifier annotation to reference a bean by name:

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

Now, let's autowire beans by type through XML configuration:

Next, let's inject a bean named item into the item property of store bean by name through XML:

We can also override the autowiring by defining dependencies explicitly through constructor arguments or setters.

9. Lazy Initialized Beans

By default, the container creates and configures all singleton beans during initialization. To avoid this, you can use the lazy-init attribute with value true on the bean configuration:

As a consequence, the item1 bean will be initialized only when it's first requested, and not at startup. The advantage of this is faster initialization time, but the trade-off is that configuration errors may be discovered only after the bean is requested, which could be several hours or even days after the application has already been running.

10. Conclusion

In this article, we've presented the concepts of inversion of control and dependency injection and exemplified them in the Spring framework.

You can read more about these concepts in Martin Fowler's articles:

  • 제어 컨테이너의 반전 및 종속성 주입 패턴.
  • 제어 반전

그리고 Spring Framework Reference Documentation에서 IoC 및 DI의 Spring 구현에 대해 자세히 알아볼 수 있습니다.