본문 바로가기

Backend/Spring

DI와 IoC

DI (의존성 주입) 이란

어떤 객체(B)를 사용하는 주체(A)가 객체(B)를 직접 생성하는게 아니라 객체를 외부에서 생성해서 사용하려는 주체 객체(A)에 주입시켜주는 방식으로,

  1. 생성자 주입 방식
  2. Setter 주입 방식
  3. 필드 주입 방식

이 있다.

객체 지향 프로그래밍에서 의존성 관리를 위한 패턴 중 하나이다. 객체 간의 결합도를 낮추고, 코드 재사용성과 유지보수성을 향상시킨다.

그 중에서도 Spring은 생성자 주입 방식을 권장한다. 먼저 각각의 예시 코드를 보자.

생성자 주입

@Service
public class AService {
    private final BService bService;

    public AService(BService bService) {
        this.bService = bService;
    }
}

스프링은 생성자가 하나일 때 자동으로 생성자를 autowire 한다. 따라서 final, @RequiredArgsConstructor 조합으로 다음과 같이 작성할 수 있다.

@Service
@RequiredArgsConstructor
public class AService {
    private final BService bService;
}

Setter 주입

@Service
public class AService {
    private BService bService;

    @Autowired
    public void setBService(BService bService) {
        this.bService = bService;
    }
}

필드 주입

@Service
public class AService {
    @Autowired
    private BService bService;
}

생성자 주입 방식을 쓰는 이유

  1. 순환 참조 문제를 컴파일시점에 알 수 있다. 순환 참조 문제를 해결 하지는 않는다. 다만, 컴파일 시점에 알 수 있다.

  2. 필드 주입과 Setter 주입 방식과 달리, 변수를 final로 설정할 수 있다. 따라서 객체의 불변성을 보장한다.

  3. 스프링 컨테이너의 도움 없이 테스트 코드를 작성할 수 있다.

    예를 들어, 다음과 같은 클래스가 있다고 가정하자:

     @Service
     public class AService {
         private final BService bService;
    
         public AService(BService bService) {
             this.bService = bService;
         }
     }

    테스트 코드를 작성할 때, 스프링 컨테이너의 도움 없이 직접 BService의 구현체를 생성하고 AService의 생성자에 주입할 수 있다. 이렇게 하면 테스트 코드에서 필요한 의존성을 손쉽게 주입할 수 있다.

     public class AServiceTest {
         @Test
         public void someTestMethod() {
             BService bService = new BServiceImpl();
             AService aService = new AService(bService);
    
             ...
         }
     }

    이처럼 생성자 주입 방식을 사용하면, 테스트 코드에서 스프링 컨테이너에 의존하지 않고도 객체 간의 의존성을 설정할 수 있어, 테스트 코드 작성이 용이해진다.

IoC (Inversion of Control)

DI ⊂ IoC

일반적인 프로그램을 생각해보자. 객체의 생명주기(객체의 생성, 초기화, 소멸, 메서드 호출 등)를 클라이언트 구현 객체가 직접 관리한다.

Inversino of Control에서는, 제어의 역전, 즉 객체의 생명주기를 객체가 직접 관리하지 않는다.

Controller, Service 같은 객체들의 동작을 우리가 직접 구현하기는 하지만, 해당 객체들이 어느 시점에 호출될 지는 신경쓰지 않는다. 단지 프레임워크가 요구하는대로 객체를 생성하면, 프레임워크가 해당 객체들을 가져다가 생성하고, 메서드를 호출하고, 소멸시킨다. 프로그램의 제어권이 역전된 것이다.

장점

  • 프로그램의 진행 흐름과 구체적인 구현을 분리시킬 수 있다.
  • 객체 간 의존성이 낮아진다.

Spring IoC 컨테이너

오브젝트(빈)의 생성과 의존 관계 설정을 스프링 컨테이너가 해준다.

그래서 스프링 컨테이너를 IoC 컨테이너라고 부른다.