
Spring Boot 의존성 주입은 처음에는 어렵게 보입니다. 그냥 `new UserService()`로 만들면 될 것 같은데, Spring은 Bean을 등록하고 생성자로 주입받는 방식을 자주 사용합니다.
핵심은 객체가 협력 객체를 직접 만들지 않고 필요한 것을 외부에서 받게 하는 것입니다. 이 글은 Spring Framework 공식 문서를 기준으로 DI, IoC Container, Bean, 생성자 주입의 의미를 정리합니다.

Spring Boot 의존성 주입을 먼저 한 문장으로 정리하면
의존성 주입은 객체가 필요한 협력 객체를 직접 만들지 않고 외부에서 받는 방식입니다. Spring에서는 IoC Container가 Bean을 만들고 연결해 줍니다.
@Service
public class OrderService {
private final PaymentClient paymentClient;
public OrderService(PaymentClient paymentClient) {
this.paymentClient = paymentClient;
}
}`OrderService`는 `PaymentClient`가 필요하다는 사실만 드러냅니다. 실제 구현을 언제 만들고 어떤 설정으로 연결할지는 Spring이 관리합니다.
new로 직접 만들면 무엇이 불편할까
작은 예제에서는 `new`가 가장 쉬워 보입니다. 하지만 객체가 늘어나면 생성 순서, 설정값, 교체 가능성, 테스트 대역까지 직접 관리해야 합니다.
public class OrderService {
private final PaymentClient paymentClient =
new RealPaymentClient("https://api.example.com");
}이 코드는 `OrderService`가 결제 클라이언트의 구체 클래스와 설정 방식까지 알고 있습니다. 테스트에서 가짜 결제 클라이언트로 바꾸기도 어렵습니다.
Bean은 Spring이 관리하는 객체다
Spring에서 Bean은 컨테이너가 생성하고 관리하는 객체입니다. Controller, Service, Repository처럼 애플리케이션 구조를 이루는 객체를 Bean으로 등록하면 Spring이 필요한 곳에 주입할 수 있습니다.
@RestController
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
}Controller는 Service를 직접 만들지 않습니다. 필요한 의존성을 생성자로 받고, Spring이 알맞은 Bean을 찾아 넣어 줍니다.
생성자 주입이 읽기 쉬운 이유
생성자 주입은 이 객체가 동작하기 위해 무엇이 반드시 필요한지 생성자에 드러냅니다. 필수 의존성을 final 필드로 둘 수 있고, 테스트에서도 직접 객체를 만들기 쉽습니다.
PaymentClient fakeClient = new FakePaymentClient();
OrderService service = new OrderService(fakeClient);Spring 컨테이너 없이도 단위 테스트에서 필요한 의존성을 넣을 수 있습니다. 그래서 생성자 주입은 코드 구조와 테스트 양쪽에서 장점이 있습니다.
DI는 결합도를 낮추는 도구다
DI의 목적은 객체 생성을 어렵게 만드는 것이 아닙니다. 객체가 구체 구현과 생성 방식에 덜 묶이게 만드는 것입니다. 이렇게 하면 구현 교체, 설정 변경, 테스트 대역 사용이 쉬워집니다.
public interface PaymentClient {
PaymentResult pay(Order order);
}서비스는 무엇을 필요로 하는지만 알고, 그것을 어떻게 만들지는 바깥에 맡기는 구조가 DI의 핵심입니다.
실무 체크리스트
- Controller, Service, Repository 사이 연결은 생성자 주입을 우선 검토한다
- 구체 클래스 생성과 설정값을 업무 로직 안에 숨기지 않는다
- 테스트에서 가짜 구현을 쉽게 넣을 수 있는지 확인한다
- Bean 등록 범위와 생명주기가 코드 의도와 맞는지 본다
- DI를 쓰더라도 도메인 값 객체까지 무조건 Bean으로 만들지는 않는다
정리
Spring Boot를 이해할 때는 문법을 짧게 쓰는 것보다, 코드가 표현해야 하는 의도를 먼저 보는 편이 좋습니다. 오늘 글의 기준은 ‘읽는 사람이 실수를 줄일 수 있는가’입니다.
함께 보면 좋은 내부 글은 자바 try-with-resources는 왜 필요할까: close를 깜빡하는 실수를 줄이는 방법, 자바 Optional은 null을 완전히 없애줄까: 써야 할 곳과 피해야 할 곳, 자바 enum은 상수 모음보다 더 강하다: 필드와 메서드를 넣는 이유입니다. 외부 기준은 Spring Framework Docs – IoC Container, Spring Framework Docs – Dependencies, Spring Framework Docs – @Autowired를 확인했습니다.