Spring/Spring 기초 및 세팅

Spring day 2

seungwon-1 2026. 6. 19. 18:54

IoC와 DI, Spring의 핵심 원리

들어가며

1일차에서 프로젝트를 만들고 화면을 띄우는 것까지 해봤다. 그 과정에서 @SpringBootApplication, @RestController 같은 어노테이션이 등장했지만, 그것들이 정확히 무슨 일을 하는지는 넘어갔다.

오늘 다룰 IoC와 DI는 그 동작의 바탕이 되는 개념이자, Spring을 Spring답게 만드는 핵심 원리다. 처음에는 용어가 추상적으로 느껴지지만, 코드로 비교해 보면 어렵지 않다.

의존성이란 무엇인가

IoC와 DI를 이해하려면 먼저 의존성(Dependency)이라는 단어부터 짚어야 한다.

어떤 클래스가 동작하기 위해 다른 클래스가 필요할 때, 앞의 클래스가 뒤의 클래스에 의존한다고 말한다. 예를 들어 주문을 처리하는 OrderService가 결제 기능을 가진 PaymentService를 사용한다면, OrderService는 PaymentService에 의존하는 것이다.

public class OrderService {
    // OrderService가 직접 PaymentService를 생성한다
    private final PaymentService paymentService = new PaymentService();

    public void order() {
        paymentService.pay();
    }
}

코드는 잘 동작한다. 하지만 한 가지 문제가 있다. OrderService가 PaymentService를 직접 new로 생성하고 있다는 점이다.

이렇게 되면 결제 방식을 다른 것으로 바꾸려 할 때마다 OrderService 내부 코드를 직접 고쳐야 한다. 또한 테스트할 때 가짜 결제 객체로 갈아끼우기도 어렵다. 두 클래스가 너무 단단하게 묶여 있는 상태이며, 이를 강한 결합(tight coupling)이라고 부른다.

IoC (제어의 역전)

IoC는 Inversion of Control, 우리말로 제어의 역전이다.

객체를 생성하고 관리하는 제어권을 개발자가 직접 쥐는 것이 아니라, 프레임워크(Spring)에게 넘기는 것을 뜻한다. 위 예제처럼 개발자가 직접 new로 객체를 만들던 것을, 이제는 Spring이 대신 만들어서 관리해 준다.

즉 "내가 필요한 객체를 직접 만든다"에서 "필요한 객체를 Spring이 만들어 건네준다"로 주도권이 뒤집힌다. 이 뒤집힘이 바로 제어의 역전이다.

DI (의존성 주입)

DI는 Dependency Injection, 의존성 주입이다. IoC라는 개념을 실제로 구현하는 구체적인 방법이라고 보면 된다.

필요한 객체를 클래스 내부에서 직접 생성하지 않고, 외부에서 만들어 넣어 주는 방식이다. 앞의 코드를 DI 방식으로 바꾸면 다음과 같다.

@Service
public class OrderService {
    private final PaymentService paymentService;

    // 직접 생성하지 않고, 외부에서 받아 온다
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void order() {
        paymentService.pay();
    }
}

차이는 분명하다. 이제 OrderService는 PaymentService를 직접 만들지 않는다. 단지 생성자를 통해 "나는 PaymentService가 필요하다"고 선언할 뿐이고, 실제 객체는 Spring이 만들어서 넣어 준다. 이 넣어 주는 과정이 곧 주입이다.

이렇게 하면 두 클래스의 결합이 느슨해진다. OrderService는 어떤 PaymentService가 들어오는지 신경 쓰지 않아도 되고, 결제 방식이 바뀌어도 OrderService 코드는 그대로 둘 수 있다. 이를 느슨한 결합(loose coupling)이라고 한다.

Spring은 이걸 어떻게 처리하는가

Spring은 내부에 컨테이너(Container)라는 공간을 두고, 거기서 객체를 만들어 보관하고 관리한다. 이렇게 Spring이 만들어서 관리하는 객체를 Bean이라고 부른다.

@Service, @Component 같은 어노테이션을 클래스에 붙이면, Spring은 그 클래스를 Bean으로 등록한다. 그리고 어떤 클래스가 다른 Bean을 필요로 하면, 컨테이너에서 알맞은 Bean을 찾아 자동으로 주입해 준다. 위 예제에서 PaymentService가 OrderService 생성자로 들어올 수 있었던 것도 둘 다 Bean으로 등록되어 있기 때문이다.

Bean과 컨테이너의 자세한 동작은 다음 편에서 따로 다룬다. 오늘은 "Spring이 객체를 대신 만들어 관리하고, 필요한 곳에 넣어 준다"는 큰 그림만 잡으면 된다.

의존성을 주입하는 세 가지 방법

주입 방식에는 크게 세 가지가 있다.

  • 생성자 주입 : 생성자를 통해 받는 방식이다. 가장 권장되는 방법이며, 위 예제가 여기에 해당한다.
  • 필드 주입 : 필드에 직접 @Autowired를 붙이는 방식이다. 간단하지만 테스트가 어려워 요즘은 잘 쓰지 않는다.
  • setter 주입 : setter 메서드를 통해 받는 방식이다. 선택적으로 바꿀 수 있는 의존성에 가끔 쓰인다.

입문 단계에서는 생성자 주입 하나만 익혀도 충분하다. 실무에서도 대부분 생성자 주입을 기본으로 사용한다.

IoC와 DI의 장점

  • 클래스 간 결합도가 낮아져 코드가 유연해진다
  • 구현체를 갈아끼우기 쉬워진다
  • 가짜 객체를 주입할 수 있어 테스트가 편해진다
  • 객체의 생성과 관리를 Spring이 대신 처리해 준다
  • 코드가 무엇에 의존하는지 생성자만 봐도 명확하게 드러난다

정리

정리해 보면, IoC는 객체의 제어권을 Spring에게 넘긴다는 개념이고, DI는 그 개념을 실제로 구현하는 방법이다. 필요한 객체를 직접 만들지 않고 Spring이 만들어 넣어 줌으로써, 클래스끼리 느슨하게 연결되고 코드가 한결 유연해진다.

처음에는 직접 new로 만드는 편이 더 직관적으로 느껴질 수 있다. 하지만 프로젝트가 커지고 클래스 사이의 관계가 복잡해질수록, 이 방식이 왜 필요한지 점점 체감하게 된다.

다음 3일차에서는 오늘 잠깐 언급한 Bean과 컨테이너, 그리고 컴포넌트 스캔을 다룬다. Spring이 어떻게 클래스를 찾아 Bean으로 등록하는지 그 과정을 살펴볼 것이다.

'Spring > Spring 기초 및 세팅' 카테고리의 다른 글

Spring day 4  (0) 2026.06.21
Spring day 3  (0) 2026.06.20
Spring day 1  (0) 2026.06.17