Spring IoC & DI

Kim Dong Kyun·2023년 3월 18일
2
post-thumbnail
post-custom-banner

이미지 출처

개요

IoC와 DI에 대해서 알아보자.


정리

  • IoC, IoC Container
  1. Application context는 BeanFactory 인터페이스를 상속받는다. 더불어 IoC Container도 상속받는다.
  2. IoC의 개념은 객체 생성과 관리를 프레임워크가 대신 해 주는 것을 말한다. 더불어 Application Context 게시글에서 정리했듯, 싱글턴 패턴을 사용해서 하나의 Bean은 하나의 Instance임을 보장받는다.
  3. 관리하는 Bean간의 DI가 가능하게 해주는 녀석이 IoC Container이다.
  • DI 세가지 방식
  1. 필드주입, 수정자주입, 생성자주입이 있고 생성자 주입이 가장 추천된다.
  2. 필드주입, 수정자 주입은 컴파일 시점에 체크되지 않는다. (의존성 주입이 런타임에 발생하기 때문이다)
  3. 생성자 주입을 권장하는 이유는 위의 이유와 더불어 불변객체임을 보장하기 때문이다.(final) -> 즉 런타임 시점에 변경이 일어나지 않는다.

1. IoC

IoC의 개념과 구성

스프링의 IoC (Inversion of Control)는 객체 생성과 관리를 개발자가 아닌 프레임워크가 대신 수행하는 개념입니다.

IoC 컨테이너는 일반적으로 컨테이너 안에 객체들을 저장하고 관리합니다. 이를 위해 컨테이너는 다음과 같은 구성 요소를 포함합니다.

  • Bean
    IoC 컨테이너에서 생성되고 관리되는 객체를 의미합니다. Bean은 애플리케이션에서 사용되는 주요 객체들을 의미하며, 컨테이너가 생성, 조립, 관리, 제거합니다.

  • Container
    Bean을 관리하는 역할을 합니다. Bean의 생성과 소멸, 의존성 주입 등을 관리하며, 애플리케이션의 생명주기와 무관(즉 실행 중 어느때든)하게 객체를 관리합니다. 대표적으로 ApplicationContext와 BeanFactory가 있습니다.

  • Dependency Injection(DI)
    Bean 간의 의존성을 주입하는 것을 의미합니다. IoC 컨테이너에서는 Bean을 생성할 때, 자동으로 의존성 주입을 해주며, 이를 통해 객체 간의 결합도를 낮추어 유연하고 확장성 있는 코드를 작성할 수 있습니다.

이러한 IoC 컨테이너의 구성 요소는 개발자가 일일이 생성, 관리하는 것보다 코드의 유연성과 확장성을 높여주기 때문에 매우 유용한 기술입니다.


IoC Container의 장점

IoC 컨테이너를 사용하면 다음과 같은 장점이 있습니다.

  • 코드의 결합도를 낮춥니다
    객체 간의 의존성을 컨테이너에게 위임함으로써, 객체 간의 결합도를 낮출 수 있습니다. 이를 통해 애플리케이션의 유연성과 확장성을 향상시킬 수 있습니다.
  • 객체의 생명주기를 관리합니다
    IoC 컨테이너는 Bean의 생성, 초기화, 소멸 등의 생명주기를 관리합니다. 이를 통해 애플리케이션의 성능과 안정성을 향상시킬 수 있습니다.
  • 의존성 주입을 제공합니다
    IoC 컨테이너는 객체 간의 의존성을 주입해줍니다. 이를 통해 개발자는 객체 간의 복잡한 의존성을 직접 관리할 필요 없이 간단하게 애플리케이션을 구성할 수 있습니다.

2.DI

DI는 객체 간의 의존성을 줄이기 위해 객체를 직접 생성하는 것이 아니라, 필요한 객체를 외부에서 전달받는 패턴입니다. 이는 코드의 유지보수성을 높이고 결합도를 낮추어 유연성을 높이는 데 도움을 줍니다.

DI의 세 가지 방식과 각각의 장단점은 다음과 같습니다.

1. 생성자 주입 (Constructor Injection): 생성자를 통해 의존성을 주입하는 방식입니다.

public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;

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

    // UserService 인터페이스 메서드 구현 생략
}

장점:

  • 컴파일 시점에 오류를 확인할 수 있습니다.
  • 의존성 주입이 필수적인 객체를 생성할 때 유용합니다.

단점:

  • 생성자 파라미터의 수가 늘어날수록 코드 가독성이 떨어집니다.
  • 객체의 생성 시점에 모든 의존성이 주입되어야 하므로, 객체 생성 시간이 늘어날 수 있습니다.

2. 수정자 주입 (Setter Injection): Setter 메서드를 통해 의존성을 주입하는 방식입니다.

public class UserServiceImpl implements UserService {

    private UserRepository userRepository;

    public UserServiceImpl() {}

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

    // UserService 인터페이스 메서드 구현 생략
}

장점:

  • 코드 가독성이 좋습니다.
  • 객체 생성 시간이 줄어들 수 있습니다.

단점:

  • 누락된 Setter가 있을 경우, Null Pointer Exception 등의 오류가 발생할 수 있습니다.
  • Setter 메서드를 통해 의존성이 주입되므로, 객체가 완전하지 않은 상태로 생성될 수 있습니다.

3. 필드 주입 (Field Injection): 필드에 직접 의존성을 주입하는 방식입니다.

public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    // UserService 인터페이스 메서드 구현 생략
}

장점:

  • 코드가 간결합니다.
  • 자동 완성 등의 기능을 통해 개발 효율성이 높아집니다.

단점:

  • 객체 생성 시점에 모든 의존성이 주입되지 않아, 객체의 상태가 완전하지 않을 수 있습니다.
  • 테스트 등에서 객체를 생성하거나, 의존성을 변경하는 것이 어렵습니다.

각각의 방식은 장단점이 있으며, 개발자는 상황에 따라 적절한 방식을 선택하여 사용해야 합니다. 일반적으로 생성자 주입 방식을 권장합니다.

post-custom-banner

0개의 댓글