Spring's IoC and DI

p-q·2023년 11월 15일
0
post-thumbnail

1. IoC(Inversion of Control) 제어의 역전

1. 개요

프로그램의 객체 또는 일부에 대한 제어를 컨테이너나 프레임워크로 이전하는 소프트웨어 엔지니어링의 원칙입니다. 예를들어 기존 프로그래밍에서는 애플리케이션의 비지니스 로직이 실행 흐름을 제어하고 라이브러리 함수나 객체를 호출합니다. 그러나 Ioc에서는 이러한 관게가 역전되어 프레임워크가 실행 흐름을 제어하고 애플리케이션 코드는 프레임워크에 의해 호출됩니다. 객체 지향 프로그래밍에서 가장 자주 사용되며 보다 모듈적이고 유연하며 쉽게 테스트할 수 있는 코드를 가능하게 합니다.

2. 작동방식

소프트웨어 개발에서 할리우드 원칙은 애플리케이션 코드가 프레임워크나 라이브러리 코드를 호출하는 대신 프레임워크가 애플리케이션 코드를 호출하는 것을 의미합니다. 이는 의존성 주입, 이벤트 중심 프로그래밍 또는 템플릿 메서드와 같은 다양한 기술을 통해 이루어집니다. 프레임워크는 객체의 수명 주기를 관리하고, 필요에 따라 인스턴스를 생성하거나 폐기하며, 객체가 생성될 때 필요한 종속성을 제공하는 역할을 담당합니다.

헐리우드 원칙
'당신이 할 일 중에서 내가 필요할 때 불러주면, 요청한 사항에 맞춰서 행동하겠다. ' 라고 정의한다. 즉, 내가 언제 어떻게 해야 할 지를 알고, 스스로 제어 할 수 있는 일이면 하되, 그밖에 영역의 일은 정의해서 알려주면 행동하겠다는 것이다.

3. 사용의 장점

  • 커플링 감소: IoC를 사용하면 컴포넌트가 하드코딩된 종속성을 가질 필요가 없으므로 애플리케이션의 여러 부분 간의 결합이 줄어듭니다.

  • 모듈성 향상: 구성 요소가 더 느슨하게 결합되므로 애플리케이션이 더 모듈화되어 이해, 유지 관리 및 확장이 더 쉬워집니다.

  • 향상된 테스트: IoC를 사용하면 종속성을 모의 또는 스텁으로 쉽게 대체할 수 있으므로 단위 테스트가 더 간단해집니다.

  • 유연성 및 재사용성 향상: 컴포넌트가 특정 구현에 엄격하게 종속되지 않으므로 재사용성과 유연성이 향상됩니다.

  • 더 쉬운 구성 및 통합: 비즈니스 로직의 변경이나 다른 시스템과의 통합은 주로 구성을 통해 최소한의 코드 변경으로 이루어질 수 있습니다.

전략 설계 패턴, 서비스 로케이터 패턴, 팩토리 패턴, 의존성 주입(DI)과 같은 다양한 메커니즘을 통해 IoC를 구현할 수 있으며 애플리케이션을 설계하고 개발하는 방식에 큰 변화를 가져와 더욱 견고하고 유지 관리가 용이하며 확장 가능한 시스템으로 이어집니다.

2. DI(Dependency Injection) 의존성 주입

DI의 정의 종속성 주입(DI)은 제어의 역전(IoC)을 구현하는 데 중요한 디자인 패턴입니다. 이는 객체가 직접 종속성을 생성하는 것이 아니라 객체에 외부 종속성을 제공하는 프로세스를 포함합니다. 이 패턴은 종속 객체의 구성과 사용법을 분리하여 보다 유연하고 유지 관리가 용이하며 테스트 가능한 코드를 만드는 데 유용합니다. DI에서 객체는 생성 시 종속성을 전달받으며, 일반적으로 객체와 종속성을 함께 어셈블하는 역할을 하는 '인젝터' 또는 '컨테이너'를 통해 종속성을 전달받습니다.

1. 유형

1. Constructor Injection(생성자 주입)

생성자 주입은 클래스 생성자를 통해 종속성이 제공됩니다. 인스턴스화할 때 필요한 종속성을 생성자에 매개변수로 전달하는 일반적인 기법입니다. 이 방법을 사용하면 객체가 항상 종속성과 함께 생성되므로 완전히 생성된 후에는 변경할 수 없습니다.

@Service
public class SomeService {
    private final UserRepository userRepository;
    private final SomeRepository someRepository;

    @Autowired
    public CoffeeServiceImpl(UserRepository userRepository, SomeRepository someRepository) {
        this.userRepository = userRepository;
        this.someRepository = someRepository;
    }
}
  • 생성자 호출 시점에 1번만 호출되는 것을 보장합니다.
  • 불변과 필수 의존 관계에 사용합니다.
  • 생성자가 1개만 존재하는 경우 @AutoWired를 생략해도 자동 주입됩니다.
  • NPE(NullPointerException)을 방지할 수 있습니다.
  • 주입받을 필드를 final로 선언 가능합니다.

    @Autowired
    필요한 의존 객체의 “타입"에 해당하는 빈을 찾아 주입한다.
    생성자, setter, 필드
    위의 3가지의 경우에 Autowired를 사용할 수 있다. 그리고 Autowired는 기본값이 true이기 때문에 의존성 주입을 할 대상을 찾지 못한다면 애플리케이션 구동에 실패한다.

2. Setter Injection(수정자 주입)

수정자 주입은 클래스의 세터 메서드를 통해 종속성을 주입하는 것입니다. 이 방법을 사용하면 객체의 인스턴스화 후에 종속성을 설정하거나 변경할 수 있으므로 유연성이 향상됩니다. 그러나 세터가 호출되지 않으면 객체가 불완전한 상태가 될 수 있습니다.

@Service
public class SomeService {
    private final UserRepository userRepository;
    private final SomeRepository someRepository;

    @Autowired
	public void setUserRepository(UserRepository userRepository) {
    	this.userRepository = userRepository
    }
    
    @Autowired
	public void setSomeRepository(SomeRepository someRepository) {
    	this.someRepository = someRepository
    }
}
  • 선택과 변경 가능성이 있는 의존 관계에 사용됩니다.
  • set필드명 메서드를 생성하여 의존 관계를 주입합니다.
  • @Autowired를 입력하지 않으면 실행이 되지 않습니다.

3. Field Injection(필드 주입)

필드 주입은 클래스의 필드에 종속성을 직접 주입합니다. 가장 간단한 형태이고 코드가 덜 필요하지만 클래스의 공용 API를 우회하기 때문에 객체 상태를 테스트하고 관리하기가 어렵기 때문에 일반적으로 선호되지 않습니다.

@Service
public class SomeService {
	@Autowired
    private final UserRepository userRepository;
    @Autowired
    private final UserRepository userRepository;
}
  • 외부에서 변경이 불가능하여 테스트하기 어려운 단점이 있습니다.
  • DI 프레임워크가 존재하지 않는다면 아무것도 할 수 없습니다.
  • 주로 애플리케이션의 실제 코드와 상관없는 특정 테스트 진행시 사용합니다.
  • 정상적으로 사용하려면 결국 Setter 가 필요합니다.
  • 만약 의존관계를 필수적으로 넣지 않으려면 @Autowired(required=false) 옵션 통해 처리 가능합니다.

필드 주입을 일반적으로 선호하지 않는 이유는 DI 컨테이너 내부에서만 작동하며, 순수 자바 코드로 테스트 하기 어러움이 있습니다. 그리고 final 키워드를 통해 불변 속성이라고 볼 수도 없고, Setter로 가변 속성이라고 볼 수도 없는 애매한 상황이 발생합니다.

2. 장점

  • 느슨한 결합: 종속성 주입(DI)은 소비자 클래스의 종속성을 핵심 동작으로부터 분리하는 역할을 합니다. 이러한 분리는 종속성의 변경이나 구현 방식이 이러한 종속성을 사용하는 클래스에 거의 또는 전혀 영향을 미치지 않음을 의미합니다. 즉, 클래스가 종속성의 세부 사항에 얽매이지 않으므로 보다 모듈화되고 적응력이 뛰어난 코드 구조가 만들어집니다.

소비자 클래스
애플리케이션에서 다른 컴포넌트나 서비스에 종속되어 작업을 수행하는 클래스입니다. 예를 들어 전자상거래 애플리케이션에 OrderProcessor 클래스가 있는 경우 이 클래스는 결제 처리를 처리하기 위해 PaymentService 클래스에 종속되고 배송 물류를 처리하기 위해 ShippingService 클래스에 종속될 수 있습니다.

  • 테스트의 간소화: 테스트 중에 클래스에 모의 또는 더미 버전의 종속성을 주입하면 단위 테스트를 더 간단하게 수행할 수 있습니다. 이 접근 방식은 종속성의 실제 구현에 의존하지 않기 때문에 테스트에 집중하고, 관리하기 쉬우며, 안정성을 높일 수 있습니다.

  • 코드 유지 관리 개선: 클래스 내에서 종속성을 생성하는 대신 종속성을 주입하면 코드가 더 깔끔하고 가독성이 높아집니다. 이 접근 방식은 클래스가 의존하는 구성 요소와 사용 방법을 명확히 하기 때문에 코드베이스를 더 쉽게 탐색하고 유지 관리할 수 있습니다.

  • 유연성 및 확장성: 의존성 주입은 애플리케이션의 유연성과 확장성에 기여합니다. 전체 코드에서 필요한 최소한의 조정만으로 개별 컴포넌트를 쉽게 교체하거나 업그레이드할 수 있습니다. 이러한 적응성은 애플리케이션의 유지보수 및 확장에 매우 중요한 역할을 합니다.

DI를 사용하는 것은 최신 소프트웨어 개발 관행에 따라 확장 가능하고 유지 관리 및 테스트가 가능한 시스템을 구축하는 데 중요한 역할을 합니다.

Spring 프레임워크의 IoC와 DI

Spring에서 IoC 및 DI 구현 Spring 프레임워크는 IoC 및 DI를 강력하게 구현하여 아키텍처의 초석으로 삼습니다. 이 프레임워크는 Bean(Spring의 객체)의 생성 및 wiring을 관리하고 사용하기 전에 모든 종속성이 충족되는지 확인합니다. 이는 빈의 인스턴스화, 구성, 어셈블리를 담당하는 IoC 컨테이너를 통해 이루어집니다.

빈 와이어링(wiring)
스프링을 사용하는 애플리케이션에서 각 객체가 필요한 다른 객체를 직접 찾거나 생성할 필요가 없다. 컨테이너가 협업할 객체에 대한 정보를 주기 때문이다. 애플리케이션 객체 간의 이러한 연관 관계 형성 작업이 바로 의존성 주입(Dependency Injection)의 핵심이며, 이를 보통 와이어링(wiring)이라 한다.

이 프로세스에는 일반적으로 구성 파일이나 어노테이션에 빈과 해당 종속성을 정의하는 작업이 포함되며, Spring 컨테이너는 빈이 생성될 때 이러한 종속성을 생성하고 주입하는 작업을 처리합니다. 이 접근 방식은 애플리케이션의 구성 및 종속성 사양을 실제 애플리케이션 코드와 분리하여 보다 깔끔하고 모듈화된 코드를 생성합니다.

1. Spring의 IoC 컨테이너

Spring의 IoC 컨테이너는 기본적으로 객체 생성, 수명 주기 관리, 구성 및 종속성 관리를 담당하는 정교한 팩토리입니다. 컨테이너는 XML 파일, 어노테이션 또는 Java 기반 구성으로 구성할 수 있습니다. 싱글톤(컨테이너당 하나의 인스턴스), 프로토타입(매번 새로운 인스턴스), 요청, 세션 등 웹 애플리케이션을 위한 여러 가지 범위의 빈을 지원하므로 빈의 수명 주기를 유연하게 관리할 수 있습니다.

2. 구성

  • XML 구성: Spring 초창기에는 XML 파일이 구성에 광범위하게 사용되었습니다. 여기에는 XML 파일에 빈과 그 종속성을 정의하는 것이 포함됩니다. Spring 컨테이너는 이 파일을 읽고 그에 따라 빈을 생성합니다. XML 구성은 빈을 관리하기 위한 중앙 집중식 장소를 제공하지만, 대규모 애플리케이션에서는 장황하고 관리하기 어려울 수 있습니다.

  • 어노테이션 기반 구성: Spring 프레임워크의 발전과 함께, 어노테이션 기반 구성이 더욱 널리 사용되고 있습니다. autowiring 종속성을 위한 @Autowired, 빈을 선언하기 위한 @Component, Java 기반 구성을 위한 @Configuration, @Bean과 같은 어노테이션이 사용됩니다. 어노테이션 기반 구성은 XML 파일의 필요성을 줄이고 코드를 더 읽기 쉽고 유지 관리하기 쉽게 만듭니다. 또한 빈 구성을 보다 세밀하게 제어할 수 있으며 최신 Java 에서 주로 사용됩니다.

전반적으로 Spring 프레임워크에서 IoC와 DI를 구현하면 Java 애플리케이션 개발이 크게 간소화됩니다. 느슨한 결합과 높은 응집력과 같은 모범 사례를 장려하여 애플리케이션의 확장성, 유지보수성, 테스트 가능성을 높입니다.

참고자료

baeldung-Intro to Inversion of Control and Dependency Injection with Spring
[Spring] IoC, DI 란?
[Spring] 스프링 의존성 주입

profile
ppppqqqq

0개의 댓글