본문 바로가기
스프링 공부

제어의 역전 IoC 개념 정리

by 코딩마스터^^ 2024. 8. 14.

IoC(Inversion of Control, 제어의 역전)에서 "제어"는 객체의 생성과 그 객체 간의 의존성 관리를 의미합니다. 이를 더 구체적으로 설명하면 다음과 같습니다

 

 

전통적인 객체 지향 프로그래밍(OOP)에서의 제어 흐름

전통적인 방식에서는 객체가 다른 객체를 필요로 할 때, 직접적으로 그 객체를 생성하거나, 메서드를 호출하여 의존성을 해결합니다. 예를 들어, 클래스 A가 클래스 B를 필요로 한다면, A는 B를 직접 생성하거나 B를 생성해 줄 수 있는 어떤 방식으로 처리합니다. 이 경우 객체 A는 다음과 같은 역할을 합니다:

  1. 객체 B를 생성하거나 가져오는 책임: A는 B를 필요로 할 때, 스스로 B를 생성하거나 적절한 방식으로 얻어옵니다.
  2. 의존성 관리: A는 B와의 의존 관계를 직접 관리합니다.

이런 방식에서는 애플리케이션의 흐름과 객체 간의 제어가 코드 내부에 강하게 결합되어 있어, 변경이나 테스트가 어려워질 수 있습니다.

제어의 역전(Inversion of Control)

IoC는 이 전통적인 제어 흐름을 뒤집는 개념입니다. 여기서 "역전된" 제어는 객체의 생성과 의존성 관리에 대한 책임이 객체 자신이 아닌 외부 컨테이너나 프레임워크로 넘어가는 것을 의미합니다.

  • 객체의 생성과 의존성 주입: 객체 A는 더 이상 직접 객체 B를 생성하거나 관리하지 않습니다. 대신, A는 외부에서 B를 주입받습니다. 이 작업을 Spring 같은 IoC 컨테이너가 담당합니다.
  • 제어의 주체가 바뀜: 객체 A가 객체 B의 생성을 제어하는 것이 아니라, IoC 컨테이너가 이 제어를 담당합니다. A는 그저 필요한 B를 전달받기만 하면 됩니다.

 

전통적방식 IoC방식 비

// 전통적인 방식
class A {
    private B b;

    public A() {
        this.b = new B();  // A가 B를 직접 생성함
    }
}

// IoC 방식
class A {
    private B b;

    // 생성자 주입
    public A(B b) {
        this.b = b;  // 외부에서 B를 주입받음
    }
}

 

 

IoC 컨테이너는 A의 인스턴스를 생성할 때, A가 필요한 B를 찾아서 주입해줍니다. A는 더 이상 B의 생성에 대해 알 필요가 없고, 이를 신경 쓰지 않습니다.

IoC의 이점

  • 유연성 증가: 객체의 생성과 의존성 관리가 외부로 이동하면서 코드의 유연성이 증가하고, 객체 간의 결합도가 낮아집니다.
  • 테스트 용이성: 객체를 테스트할 때, 실제 구현 대신 테스트 목업(Mock)을 주입할 수 있어 단위 테스트가 쉬워집니다.
  • 유지보수성 향상: 코드의 의존성 관리가 단순해지면서 유지보수가 용이해집니다.

 

스프링의 경우

 

Step 1: 컴포넌트 어노테이션 추가

import org.springframework.stereotype.Service;
import org.springframework.stereotype.Repository;

@Service  // 스프링이 관리할 서비스 객체로 표시
public class BookService {
    private final BookRepository bookRepository;

    // 생성자 주입 방식
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;  // 외부에서 주입 받음
    }

    public void saveBook(Book book) {
        bookRepository.save(book);
    }
}

@Repository  // 스프링이 관리할 레포지토리 객체로 표시
public class BookRepository {
    public void save(Book book) {
        // 책 저장 로직
    }
}

 

Step 2: 스프링 컨테이너 설정 (ApplicationContext)

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Application {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        BookService bookService = context.getBean(BookService.class);
        bookService.saveBook(new Book("스프링 입문서"));
    }
}

 

Step 3: 스프링 설정 클래스

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.example")  // 컴포넌트 스캔으로 BookService와 BookRepository 자동 등록
public class AppConfig {
    // 추가적인 설정이 필요한 경우 여기서 빈(Bean)을 정의할 수 있습니다.
}

 

설명

  1. @Service@Repository 어노테이션: 이 어노테이션들은 해당 클래스가 스프링 컨테이너에 의해 관리되어야 하는 컴포넌트임을 나타냅니다. @Service는 비즈니스 로직을 수행하는 클래스에, @Repository는 데이터 접근을 담당하는 클래스에 붙입니다.
  2. 의존성 주입: BookService는 이제 BookRepository를 직접 생성하지 않고, 스프링 컨테이너가 BookRepository 객체를 주입해 줍니다. 이는 생성자 주입 방식으로 이루어지며, 스프링이 BookService 객체를 생성할 때 자동으로 BookRepository를 주입해 줍니다.
  3. 스프링 컨테이너(ApplicationContext): ApplicationContext는 스프링 컨테이너의 핵심으로, 설정된 컴포넌트를 스캔하여 관리하고, 필요한 경우 자동으로 의존성을 주입합니다.

이 방식으로 BookService는 BookRepository가 어떻게 생성되는지 알 필요가 없으며, 오직 주입받은 BookRepository를 사용하기만 하면 됩니다. 이는 코드의 결합도를 낮추고, 테스트와 유지보수가 훨씬 용이해집니다.

댓글