DI

DI란 Dependency Injection의 약자로, 의존성(Dependency)을 외부에서 주입(Injection) 시켜주는 소프트웨어 설계 원칙 입니다.
Spring boot는 이 DI 원칙을 핵심 기반으로 삼아서 객체 생성과 관리, 주입 등을 자동화 하는 프레임워크 입니다.
즉 DI를 개발자가 직접 구현하지 않더라도 Spring이 대신 처리해주는 구조 입니다.
DI는 Spring boot 안에서 결합도를 낮추고 코드의 재사용성을 높여주는 역할을 하게 됩니다.

위 사진은 start.spring.io에서 Spring boot 프로젝트를 생성할 때 보게되는 화면입니다.
오른쪽에서 프로젝트에 필요한 라이브러리를 선택할 수 있습니다.
선택한 라이브러리들은 대부분 스프링 컨테이너에 자동 등록되며, DI 방식으로 연결되어 사용됩니다.
DI의 필요성
그렇다면 왜 DI를 사용하는 걸까요?
class OrderService {
private final PaymentService payment = new PaymentService();
}
위 코드로 예시를 들자면 OrderService 클래스가 PaymentService를 직접 new로 만들고 있습니다.
이러한 구조를 직접 의존이라고 부릅니다. 이런 구조는 코드가 서로 너무 얽혀 있어서 바꾸기 힘들다는 단점이 있습니다.
또한 직접 의존 방식을 사용하게 되면 여러가지 단점이 발생하게 됩니다.
강한 결합 - OrderService는 항상 PaymentService만 써야 합니다.
테스트 어려움 - 테스트 할 때도 PaymentService가 같이 실행되게 됩니다. 가짜 결제 객체로 테스트를 하지 못하게 됩니다.
재사용 어려움 - 다른 프로젝트에서 사용하려 해도 내부에서 뭘 직접 만들고 있어서 가져다가 쓰지 못하게 됩니다.
유지보수 어려움 - 결제 방식 하나 바꾸려면 여기저기 다 열어서 수정해야 합니다.
그렇다면 DI를 쓰면 어떻게 바뀌게 될까요?
class OrderService {
private final PaymentService payment;
public OrderService(PaymentService payment) {
this.payment = payment;
}
}
직접 만들지 않고 밖에서 넣어주면, 결제 수단이 뭔지 몰라도 됩니다.
테스트 할 땐 테스트를 위한 가짜 객체도 쉽게 넣을 수 있습니다.
이로 인해 DI를 사용하게 되면
내부 구현이 바뀌어도 사용하는 쪽 코드는 바꾸지 않아도 되는등의 유지보수에 용이합니다. 또한
가짜 객체를 사용해서 테스트를 보다 편하게 할 수 있습니다.
다양한 구현체를 쉽게 교체하여 유연한 구조를 만들 수 있고
결합도가 낮아져, 각 객체가 서로에 대해 깊이 알지 않아도 되는 등의 장점이 생기게 됩니다.
DI의 동작 과정
그렇다면 이 DI는 내 코드 안에서 어떻게 작동할까요?
@Component
public class Engine {
public void start() {
System.out.println("Engine started");
}
}
@Component
public class Car {
private final Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
public void drive() {
engine.start();
System.out.println("Car is driving");
}
}
위 코드를 예시로 들어서 설명 해 보겠습니다.
1. 객체 생성
스프링 컨테이너가 애플리케이션 시작 시 @Component나 @Service, @Repository, @Controller 등의
어노테이션이 붙은 클래스를 스캔하고, 객체(빈)를 생성합니다.
2. 의존 관계 확인
스프링은 생성된 객들 간에 어떤 의존성이 필요한지 확인합니다.
생성자, 필드, 메서드에 있는 @Autowired, @Inject 등을 통해 어떤 의존성이 필요한지 분석합니다.
3. 의존 객체 주입
스프링 컨테이너가 필요한 의존성(Engine)을 찾아 Car 객체에 주입합니다.
(주입 방식에는 생성자 주입, 필드 주입, 세터 주입이 있습니다.)
4. 객체 완성 및 관리
모든 의존 관계가 주입되면, 객체는 완전한 상태가 되고 컨테이너가 해당 객체 관리합니다.
5. 의존성 사용
개발자는 직접 의존 객체를 생성하거나 관리하지 않고, 필요한 객체를 주입받아 사용합니다.
DI의 단점
그렇다면 DI도 단점이 있을까요? 결론부터 말하자면 단점이 있긴합니다. 다만
기능적인 문제의 단점이 아닌 개발자 입장에서 어려울 수 있다는 단점이 있습니다.
학습 난이도 - 초보자에게는 자동으로 연결되는 구조가 추상적으로 느껴질 수 있습니다.
디버깅 어려움 - 객체가 어디서, 어떻게 주입되었는지 추적이 어려울 수 있습니다.
과도한 추상화 - 너무 분리하면 실제 실행 흐름 파악이 어려워 질 수 있습니다.
하지만 이러한 단점들은 Spring에서 자동 주입과 Annotation 기반 구성 덕분에 대부분 해결되게 됩니다.
다른 언어에서의 DI
DI는 어떤 언어에 국한되어있지 않습니다. 그렇다면 다른 언어에서도 DI를 사용 할 수 있을까요?
Kotlin이나 Python 백엔드에서도 DI를 사용할 수 있습니다. 다만 언어나 프레임워크에 따라 방식이 조금 다릅니다.
우선 Kotlin 언어를 기반으로 하는 Spring에서는 Java 기반 Spring과 같이 DI가 매우 자연스럽게 이루어 집니다.
하지만 Python 백엔드를 할 때는 Python 자체가 동적 언어이기 때문에 정적 타입 기반 DI처럼 자동 주입을 하기는 어렵습니다.
하지만 프레임워크가 일부 지원 하거나, 명시적으로 구현 할 수는 있습니다.
정리
DI를 마지막으로 정리해 보겠습니다.
DI란 의존성을 외부에서 주입해주는 설계 원칙 입니다.
예를 들자면 만약 내가 어떤 음료수가 마시고 싶은데,
직접 사러가는 대신, 누군가가 내가 좋아하는 음료수를 골라서 가져다줄때
나는 "음료수"에 의존하고 있는 상태이고
이걸 내가 직접 만들지 않고 누군가가 주입해주는 것이 DI의 개념 입니다.
DI는 객체간 결합도를 낮추고 유연한 구조를 만들 수 있게 해줍니다
Spring boot는 이 DI를 자동으로 처리해서 개발자가
직접 객체를 생성하거나 연결하지 않아도 되게 돕습니다.
이를 통해 유지보수가 쉬워지고 테스트와 코드 재사용이 용히해지는 등의
여러 가지 장점이 생깁니다.
처음 공부하거나, 디버깅 할 때 어려움이 있을 수 있지만
Spring의 어노테이션 기반 설정이 이 문제를 완화해 줄 수 있습니다.
또한 DI는 특정 언어에 국한되지 않아서 Kotlin이나 Python등
다른 백엔드 언어와 프레임워크에서도 사용할 수 있습니다.
이상으로 블로그를 마치겠습니다. 읽어주셔서 감사합니다.
'Spring' 카테고리의 다른 글
| [Spring] Servlet과 DispatacherServlet (2) | 2025.08.01 |
|---|---|
| [Spring] Spring Filter란? (2) | 2025.08.01 |
| [Spring] PSA란? (0) | 2025.07.22 |
| [Spring] IoC란? (0) | 2025.07.17 |
| [Spring] 어노테이션과 import는 무엇일까? (1) | 2025.06.09 |