
어떤 객체가 작업을 수행하기 위해 다른 객체를 직접 생성하거나 사용해야 할 때 그 관계를 ‘의존한다’고 표현한다.
예를 들어, Roel 객체가 Galaxy 객체를 직접 생성하고 사용한다면,
Roel → Galaxy 로 의존성이 존재한다.
💡 요약
한 객체의 코드에서 다른 객체를 생성하거나 호출하면 의존성이 생긴 것이다.
의존성의 강도를 ‘결합도(Coupling)’라고 한다.
| 결합도 | 특징 |
|---|---|
| 강한 결합 (Tight Coupling) | 특정 구현 클래스에 직접 의존 → 수정 어려움 |
| 느슨한 결합 (Loose Coupling) | 인터페이스 기반으로 의존 → 유연하고 교체 쉬움 |
public class Roel {
private Galaxy phone;
public Roel() {
this.phone = new Galaxy();
}
}
만약 Galaxy 대신 IPhone을 사용하고 싶다면?
→ Roel 내부 전체 코드를 수정해야 한다.
이처럼 구현 클래스에 강하게 묶여 있으면 변경에 취약하다.
인터페이스를 도입하면 구현(Mobile 기종)이 바뀌어도
Roel 클래스는 Phone 인터페이스만 알고 있으면 된다.
public interface Phone {
void powerOn();
void payment();
}
public class Galaxy implements Phone { ... }
public class IPhone implements Phone { ... }
public class Roel {
private final Phone phone;
public Roel(Phone phone) {
this.phone = phone;
}
}
Roel은 더 이상 Galaxy, IPhone이 무엇인지 알 필요가 없다.
→ 유연한 구조, 유지보수성 UP!
DI는 말 그대로 의존성(Dependency)을 외부에서 주입(Injection)받는 방식이다.
즉, “필요한 객체를 스스로 new 로 만들지 않고, 외부에서 넣어주는 방식이다.”
💡 DI란?
객체가 사용할 객체의 생성 책임을 외부(스프링 컨테이너)에 넘기고
자신은 주입받은 객체만 사용한다.
이는 스프링이 지향하는 IoC(제어의 역전) 개념을 구체화한 것이다.
그래서 스프링을 IoC 컨테이너 혹은 DI 컨테이너라고도 부른다.
스프링에서는 다음과 같은 방식으로 객체를 주입한다.
@Component
public class Roel {
private final Phone phone;
@Autowired
public Roel(Phone phone) {
this.phone = phone;
}
}
final로 선언 가능 → 값 변경 방지@Autowired 생략 가능✔ 스프링 공식 권장 방식
✔ 반드시 필요하고 변경되면 안 되는 의존성에 적합
✔ 순환 참조를 애플리케이션 구동 시점에 잡아줌
@Component
public class Roel {
private Phone phone;
@Autowired
public void setPhone(Phone phone) {
this.phone = phone;
}
}
@Component
public class Roel {
@Autowired
private Phone phone;
}
⚠ 스프링 공식적으로 권장하지 않는 방식
(테스트 불가 + 유지보수 어려움)
Setter/필드 주입은 객체 생성 이후 주입되므로
순환 참조가 나중에 발생하는 반면,
생성자 주입은 애플리케이션 시작과 함께 에러를 즉시 감지한다.
필요한 의존성이 없으면 객체 생성 자체가 불가능하기 때문에
설계 오류를 예방할 수 있다.
✔ 객체 간 의존성을 외부에서 주입받는 구조
✔ 강한 결합을 느슨한 결합으로 전환
✔ 유지보수성과 테스트 용이성 향상
✔ 생성자 주입이 가장 권장됨
✔ 인터페이스 기반 설계로 유연한 구조 가능
DI는 단순히 “객체를 대신 넣어주는 기술”이 아니라, 스프링이 추구하는 설계 철학 전체를 지탱하는 핵심 원리다.
객체 간 결합을 최소화하면 시스템은 자연스럽게 더 유연해지고, 변경에 강해지고, 테스트가 쉬워진다.
이는 곧 개발 생산성 향상, 유지보수 비용 절감, 안정적인 서비스 운영으로 이어진다.
또한 DI는 인터페이스 기반 설계를 자연스럽게 유도하기 때문에, 애플리케이션 구조를 확장 가능하고 견고하게 만든다.
로직 변경, 기능 추가, 의존성 교체가 잦은 실제 개발 환경에서 DI는 개발자에게 변화에 흔들리지 않는 코드 기반을 제공한다.
결국 DI는 “스프링을 스프링답게 만드는 근본 개념”이며, 스프링을 활용하는 프로젝트에서 핵심 설계 중심축 역할을 한다.