[Spring] 04. DI란?

[Spring] 04. DI란?

이전 글에서는 IoC에 대해 살펴봤습니다. IoC는 객체를 개발자가 직접 생성하고 관리하는 것이 아니라, Spring이 대신 생성하고 관리하는 개념입니다.

이번 글에서는 IoC와 함께 자주 등장하는 DI에 대해 정리하겠습니다. DI는 Spring을 이해할 때 반드시 알아야 하는 핵심 개념입니다.


1. DI란?

DI는 Dependency Injection의 줄임말입니다. 한국어로는 의존성 주입이라고 부릅니다.

여기서 의존성이란 어떤 클래스가 다른 클래스를 필요로 하는 관계를 말합니다. 예를 들어 BoardController가 게시글 기능을 처리하기 위해 BoardService를 사용한다면, BoardController는 BoardService에 의존한다고 말할 수 있습니다.

BoardController
 → BoardService 필요

DI는 이 필요한 객체를 클래스 내부에서 직접 만들지 않고, 외부에서 넣어주는 방식입니다. Spring에서는 필요한 객체를 Spring Container가 준비하고, 필요한 클래스에 연결해줍니다.


2. 의존성이 있는 코드

먼저 일반적인 Java 방식으로 객체를 직접 생성하는 코드를 보겠습니다.

public class BoardController {

    private BoardService boardService = new BoardService();

    public void write() {
        boardService.writeBoard();
    }
}

이 코드는 BoardController 안에서 BoardService 객체를 직접 생성합니다. 처음에는 단순해 보이지만, BoardController가 BoardService 생성 방식까지 알고 있어야 합니다.

이렇게 되면 두 클래스가 강하게 연결됩니다. BoardService를 다른 방식으로 바꾸거나 테스트용 객체로 교체하려고 할 때 BoardController 코드도 함께 수정해야 할 수 있습니다.


3. DI를 적용한 코드

DI를 적용하면 BoardController가 BoardService를 직접 만들지 않습니다. 대신 외부에서 전달받아 사용합니다.

public class BoardController {

    private final BoardService boardService;

    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }

    public void write() {
        boardService.writeBoard();
    }
}

이제 BoardController는 BoardService를 직접 생성하지 않습니다. 생성자를 통해 필요한 객체를 전달받습니다.

객체를 누가 만들어서 넣어줄까요? Spring에서는 Spring Container가 객체를 만들고, 필요한 곳에 넣어줍니다. 이것이 DI입니다.

Spring Container
 → BoardService 객체 생성
 → BoardController에 전달

4. Spring에서 DI 사용하기

Spring에서는 Controller, Service 같은 클래스를 Bean으로 등록하고, 필요한 객체를 자동으로 연결할 수 있습니다.

@Controller
public class BoardController {

    private final BoardService boardService;

    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }
}
@Service
public class BoardService {

    public void writeBoard() {
        System.out.println("게시글 등록 처리");
    }
}

BoardController는 BoardService가 필요합니다. BoardService는 @Service를 통해 Spring이 관리하는 객체가 됩니다. 그리고 BoardController의 생성자에 BoardService가 필요하다는 정보가 있으므로 Spring이 자동으로 연결해줍니다.

즉, 개발자가 직접 new BoardService()를 작성하지 않아도 됩니다. 필요한 객체를 Spring이 찾아서 넣어줍니다.


5. DI 방식의 종류

DI에는 대표적으로 세 가지 방식이 있습니다.

생성자 주입 생성자를 통해 필요한 객체를 전달받는 방식
필드 주입 필드에 직접 객체를 주입하는 방식
Setter 주입 setter 메서드를 통해 객체를 전달받는 방식

이 중에서 가장 많이 권장되는 방식은 생성자 주입입니다. 객체가 생성될 때 필요한 의존성이 함께 전달되기 때문에, 필요한 값이 빠진 상태로 객체가 만들어지는 일을 줄일 수 있습니다.


6. 생성자 주입

생성자 주입은 생성자를 통해 필요한 객체를 전달받는 방식입니다.

@Controller
public class BoardController {

    private final BoardService boardService;

    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }
}

이 방식은 의존성이 명확하게 보입니다. BoardController를 만들기 위해서는 BoardService가 필요하다는 사실이 생성자에 드러납니다.

또한 final 키워드를 사용할 수 있어, 한 번 주입된 객체가 중간에 바뀌지 않도록 만들 수 있습니다.


7. 필드 주입

필드 주입은 필드에 직접 객체를 주입하는 방식입니다.

@Controller
public class BoardController {

    @Autowired
    private BoardService boardService;
}

코드는 짧아 보이지만, 의존성이 겉으로 잘 드러나지 않습니다. 또한 객체를 직접 생성해서 테스트하거나 확인할 때 불편할 수 있습니다.

그래서 처음 공부할 때는 필드 주입보다 생성자 주입을 기준으로 익히는 것이 좋습니다.


8. Setter 주입

Setter 주입은 setter 메서드를 통해 필요한 객체를 전달받는 방식입니다.

@Controller
public class BoardController {

    private BoardService boardService;

    public void setBoardService(BoardService boardService) {
        this.boardService = boardService;
    }
}

Setter 주입은 객체가 생성된 후에 의존성을 넣을 수 있습니다. 필요에 따라 의존성을 바꿀 수 있다는 장점은 있지만, 반드시 필요한 객체가 빠진 상태로 사용될 가능성도 있습니다.

그래서 꼭 필요한 의존성은 생성자 주입으로 처리하는 것이 더 안전합니다.


9. IoC와 DI의 차이

IoC와 DI는 함께 등장하지만 같은 말은 아닙니다.

IoC 객체 관리의 주도권이 Spring으로 넘어가는 개념
DI 필요한 객체를 외부에서 넣어주는 방식

IoC는 큰 개념입니다. 그리고 DI는 IoC를 구현하는 대표적인 방법입니다.

IoC: 객체 관리를 Spring이 담당한다.
DI: 필요한 객체를 Spring이 넣어준다.

Spring에서는 IoC Container가 Bean을 관리하고, DI를 통해 필요한 Bean을 연결합니다. 이 흐름을 이해하면 Spring의 객체 관리 방식이 훨씬 명확해집니다.


10. DI를 사용하면 좋은 점

DI를 사용하면 객체를 직접 생성하는 코드가 줄어들고, 클래스 사이의 연결이 느슨해집니다.

  • 객체 생성 코드를 줄일 수 있습니다.
  • 클래스의 역할이 더 명확해집니다.
  • 의존 관계를 쉽게 확인할 수 있습니다.
  • 코드를 변경할 때 영향 범위를 줄일 수 있습니다.
  • 테스트용 객체로 교체하기 쉬워집니다.

게시판 프로젝트를 예로 들면, Controller는 요청 처리에만 집중하고, Service는 게시글 등록 규칙에 집중할 수 있습니다. DB 접근은 Mapper나 Repository가 맡게 됩니다.

이렇게 각 클래스가 자기 역할에 집중하면 기능이 늘어나도 코드 흐름을 파악하기 쉬워집니다.


자주 하는 실수

DI를 공부할 때 가장 흔한 실수는 Spring을 사용하면서도 클래스 안에서 직접 new를 사용하는 것입니다.

@Controller
public class BoardController {

    private BoardService boardService = new BoardService();
}

이렇게 작성하면 Spring이 객체를 관리하는 장점이 줄어듭니다. Spring을 사용할 때는 필요한 객체를 직접 만들기보다, Bean으로 등록하고 주입받는 흐름을 사용하는 것이 좋습니다.

또 다른 실수는 모든 곳에 @Autowired를 무작정 붙이는 것입니다. 어떤 객체가 필요한지 먼저 확인하고, 생성자 주입을 기준으로 작성하는 습관을 들이는 것이 좋습니다.


정리

DI는 Dependency Injection의 줄임말이며, 의존성 주입이라고 부릅니다. 어떤 클래스가 필요한 객체를 직접 생성하지 않고, 외부에서 전달받아 사용하는 방식입니다.

Spring에서는 Spring Container가 Bean을 만들고, 필요한 곳에 Bean을 주입합니다. 이 덕분에 객체 생성 코드가 줄어들고, 클래스 사이의 연결이 느슨해집니다.

DI 방식에는 생성자 주입, 필드 주입, Setter 주입이 있습니다. 이 중에서 처음 공부할 때는 생성자 주입을 기준으로 이해하는 것이 좋습니다.


연습해보기

아래 흐름을 직접 코드로 생각해보세요.

MemberController
 → MemberService
 → MemberMapper

MemberController가 MemberService를 직접 new로 만들지 않고, 생성자를 통해 전달받도록 작성해보면 DI 구조를 이해하기 쉽습니다.

다음 글에서는 Spring이 관리하는 객체인 Bean에 대해 정리하겠습니다.

'Tech Stack > Spring Boot' 카테고리의 다른 글

[Spring] 06. Spring Container와 ApplicationContext  (0) 2026.06.30
[Spring] 05. Bean이란?  (0) 2026.06.29
[Spring] 03. IoC란?  (0) 2026.06.27
[Spring] 02. Spring Framework란?  (0) 2026.06.26
[Spring] 01. Spring이 필요한 이유  (0) 2026.06.25