Spring

Spring IoC(Inversion of Control, 제어의 역전)와 DI(Dependency Injection)

개발자 May 2024. 10. 30.

Spring IoC (Inversion of Control, 제어의 역전)

  • IoC(Inversion of Control)는 객체의 생성과 생명 주기 관리, 의존성 주입 등을 Spring 프레임워크개발자를 대신해서 처리하는 개념이다. 자주 사용되는 객체는 Bean으로 등록하여 사용할 수 있고, 개발자는 이러한 객체들의 생명 주기를 관리할 필요가 없다. 이를 통해 개발자는 객체 생성과 관리에 신경 쓸 필요 없이, 비즈니스 로직에 더 집중할 수 있게 된다.
  • IoC 컨테이너: 객체의 생성, 초기화, 의존성 주입, 소멸을 관리하는 곳으로, 설정된 Bean을 자동으로 생성하고, 의존 관계를 처리한다. BeanFactory 인터페이스는 각종 객체(Bean)을 관리하는 매커니즘을 제공하고, ApplicationContext 인터페이스는 BeanFactory의 하위 인터페이스로 더 쉬운 AOP 기능과의 통합, 국제화 메시지 리소스 처리 등 엔터프라이즈별 기능이 추가되었고 Spring에서 가장 일반적으로 사용되는 IoC 컨테이너이다.

IoC컨테이너의 동작 과정

  1. 객체 생성: Spring IoC 컨테이너는 @Configuration, @Component, @Service 등의 애노테이션이나 설정 파일을 통해 Bean 정의를 읽고, 메모리에 로딩하여 인스턴스화한다.
  2. 의존성 주입: 생성된 Bean에 필요한 의존성을 자동으로 주입한다. 주입 방식은 생성자, 세터, 필드 주입 방식 중 하나이다.
  3. 초기화: Bean의 초기화 메서드(@PostConstruct)가 실행된다. 이를 통해 Bean이 필요로 하는 초기화 작업이 수행된다.
  4. 사용: 생성된 Bean은 애플리케이션의 비즈니스 로직에서 자유롭게 사용된다.
  5. 소멸: 애플리케이션이 종료되거나 Bean이 더 이상 필요하지 않을 때, IoC 컨테이너는 소멸 메서드(@PreDestroy)를 호출해 Bean을 정리한다.

DI (Dependency Injection, 의존성 주입)

  • DI(Dependency Injection)IoC 개념을 실현하는 기능으로, 의존성 주입을 통해 객체 간의 결합도를 낮추고, 유연한 코드를 작성할 수 있게 한다. DI를 통해 객체는 필요한 의존성을 직접 생성(new)하는 대신, Spring IoC 컨테이너로부터 주입받는다. 이렇게 주입받음으로써 객체 간 강한 결합도를 피하고, 테스트 가능성이 높아지며, 유연한 변경이 가능하게 된다.

DI 방식은 생성자 주입, 세터 주입, 필드 주입으로 구분되며, 일반적으로 생성자 주입이 권장된다. 이러한 방식 중 한가지를 택해 코드를 작성하면, IoC 컨테이너는 런타임 시점에 필요한 객체를 자동으로 주입한다.

DI 방식

1. Setter-based Injection (세터 주입)
세터 메서드를 통해 의존성을 주입한다.

@Service
public class UserService {
    private UserRepository userRepository;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

2. Constructor-based Injection (생성자 주입)

생성자를 통해 의존성을 주입한다. Spring에서는 @Autowired가 없어도 자동으로 생성자를 통해 의존성을 주입할 수 있으며, final 키워드를 사용하여 불변성을 보장할 수 있다.

// Case 1. 일반적인 생성자 주입
@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

// Case 2. Lombok의 @RequiredArgsConstructor로 간편하게 생성자 주입을 설정
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
}

3. Field-based Injection (필드 주입)

필드에 직접 주입하는 방식으로, Spring에서 권장되지 않으며 현시점 Spring 공식 문서의 의존성 주입 파트에 존재하지 않는다.

대표적인 문제점은 아래와 같다.

  • 필드에 final 선언이 불가하여 불변 객체로 관리할 수 없다.
  • 필수적인 종속성임에도 제대로 초기화되지 않을 수 있음(컨테이너 밖에서 new 키워드를 통한 객체 생성 시)
  • 순환 종속성을 감지하기 어려움
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
}

😎 Constructor-based Injection(생성자 주입) VS Setter-based Injection(세터 주입)

Spring 공식 문서에 따르면, 생성자 주입세터 주입혼합하여 사용할 수 있으며, 다음과 같은 기준을 따르는 것이 권장된다.

 

  • 필수 의존성에는 생성자 주입을 사용한다.
  • 선택적 의존성(예: 환경에 따른 설정, 외부 서비스 사용 여부에 따른 설정 등)에는 세터 주입을 사용한다.

 

일반적으로는 생성자 주입 방식이 더 권장되는데, 그 이유는 불변성 보장, null-safety 등의 장점을 제공하기 때문이다. 반면, 세터 주입 방식객체 생성 후에도 의존성을 재설정할 수 있는 유연성을 제공하지만, null 값을 수동으로 체크해야 하거나 객체의 상태가 변경될 수 있는 단점이 존재한다.

따라서, 필수 의존성을 처리할 때는 생성자 주입을 사용하고, 선택적 의존성이나 유연한 재설정이 필요한 경우에는 세터 주입을 사용하는 것이 적합하다.


 

Reference

 

 

댓글