Programming/Spring
Spring 의존성 주입
khj1999
2025. 2. 6. 21:00
🚀 의존성 주입(Dependency Injection, DI)을 하는 이유
스프링에서 의존성 주입(DI) 을 사용하는 이유는 객체 간의 결합도를 낮추고 유지보수성을 높이기 위해서야. DI를 사용하면 코드가 더 유연하고 확장 가능해진다.
1. 객체 간의 결합도(Dependency)를 낮추기 위해
- DI를 사용하면 객체 간의 강한 결합도를 줄이고 느슨한 결합(Loosely Coupled)을 유지할 수 있다.
- 예를 들어, 아래처럼 직접 객체를 생성하면 Car 클래스는 Engine 구현체에 강하게 의존하게 됨.
❌ 의존성 주입 없이 직접 객체 생성 (강한 결합)
public class Car {
private Engine engine = new Engine(); // 직접 객체 생성
public void start() {
engine.run();
}
}
- 만약 Engine을 ElectricEngine으로 바꾸려면 Car 코드를 수정해야 한다!
- 코드 수정 없이 쉽게 변경할 수 있도록 DI를 적용하는 게 좋음.
✅ 의존성 주입을 활용한 코드 (느슨한 결합)
public class Car {
private final Engine engine;
public Car(Engine engine) { // 생성자를 통한 DI
this.engine = engine;
}
public void start() {
engine.run();
}
}
이제 Car 클래스는 특정 엔진 구현체에 의존하지 않으며, DI를 통해 ElectricEngine을 쉽게 주입할 수 있다.
Engine engine = new ElectricEngine();
Car car = new Car(engine);
2. 객체의 재사용성과 확장성을 높이기 위해
- DI를 사용하면 객체를 쉽게 교체할 수 있어서 재사용성이 좋아짐.
- 인터페이스 기반 프로그래밍이 가능하므로 새로운 구현체를 추가하기 쉬움.
예를 들어, Engine 인터페이스를 사용하면 Car 클래스의 코드 변경 없이 다른 엔진으로 교체 가능하다
public interface Engine {
void run();
}
public class GasolineEngine implements Engine {
public void run() {
System.out.println("Gasoline engine running!");
}
}
public class ElectricEngine implements Engine {
public void run() {
System.out.println("Electric engine running!");
}
}
이제 Car는 다양한 엔진을 주입받을 수 있다.
Car car1 = new Car(new GasolineEngine()); // 휘발유 엔진
Car car2 = new Car(new ElectricEngine()); // 전기 엔진
3. 코드의 유지보수성을 높이기 위해
- 의존성을 직접 관리하는 대신 스프링 컨테이너가 주입해 주므로 객체 생성을 신경 쓰지 않아도 됨.
- 예를 들어, @Autowired를 사용하면 자동으로 객체가 주입됨.
@Component
public class Car {
private final Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
}
- 만약 Engine 구현체가 바뀌어도 Car 코드를 수정할 필요 없이 스프링이 알아서 주입해 줌!
4. 테스트가 쉬워짐 (유닛 테스트 가능)
- DI를 사용하면 테스트 시 Mock 객체를 주입할 수 있어서 단위 테스트가 훨씬 쉬워짐.
@Test
void carShouldStartWithMockEngine() {
Engine mockEngine = mock(Engine.class); // 가짜 객체(Mock) 생성
Car car = new Car(mockEngine);
car.start();
verify(mockEngine).run(); // Mock 객체의 메서드 호출 검증
}
- 직접 객체를 생성하면 테스트할 때 원하는 객체로 대체하기 어렵지만, DI를 사용하면 가짜 객체(Mock)를 쉽게 넣을 수 있음.
5. 스프링 컨테이너의 관리 덕분에 더 효율적인 리소스 사용 가능
- DI를 사용하면 객체를 직접 생성하는 대신 스프링 컨테이너가 싱글톤으로 관리함.
- 따라서 불필요한 객체 생성을 줄이고 메모리를 효율적으로 사용할 수 있음.
@Component
public class Engine {
public Engine() {
System.out.println("Engine 객체 생성됨!");
}
}
- 스프링 컨테이너가 알아서 객체를 한 번만 생성하고 필요한 곳에 주입함.
📌 결론: DI는 유지보수성과 확장성을 높이고, 객체 재사용성을 극대화함
DI를 사용하는 이유 이점
결합도를 낮춤 | 특정 구현체에 종속되지 않고 쉽게 변경 가능 |
재사용성 증가 | 객체를 교체하거나 확장하기 쉬움 |
유지보수성 향상 | 코드 수정 없이 의존성 변경 가능 |
테스트 용이 | Mock 객체를 활용한 유닛 테스트 가능 |
리소스 효율성 증가 | 스프링 컨테이너가 객체를 효율적으로 관리 |
🔥 즉, DI를 사용하면 코드가 더 유연하고 유지보수하기 쉬워진다! 💡
의존성 주입의 방식
자바 스프링의 의존성 주입(Dependency Injection, DI) 방식은 크게 세 가지로 나눌 수 있다.
1. 생성자 주입 (Constructor Injection)
📌 개념
- 생성자를 이용해 의존성을 주입하는 방식
- 주입받을 객체를 생성자의 매개변수로 전달
✅ 예제
@Component
public class Car {
private final Engine engine;
@Autowired // 스프링 4.3부터는 생략 가능
public Car(Engine engine) {
this.engine = engine;
}
}
🏆 장점
- 불변성 유지: final 키워드를 사용하여 불변 객체를 만들 수 있음
- 순환 참조 방지: 생성자 주입 시 순환 참조가 발생하면 스프링이 애플리케이션 실행 시점에서 오류를 감지
- 테스트 용이: 객체를 명확하게 주입하기 때문에 테스트 시 명시적인 의존성 주입 가능
❌ 단점
- 필요 없는 의존성까지 강제할 가능성이 있음
- 의존성이 많을 경우 생성자 코드가 길어질 수 있음
2. 필드 주입 (Field Injection)
📌 개념
- 필드에 직접 @Autowired 어노테이션을 붙여 주입하는 방식
✅ 예제
@Component
public class Car {
@Autowired
private Engine engine;
}
🏆 장점
- 코드가 간결하며, 작성이 쉽고 직관적임
- 별도의 생성자나 Setter가 필요 없음
❌ 단점
- 의존성 변경이 어려움 (Setter가 없기 때문에 테스트에서 Mock 객체를 주입하기 어려움)
- 순환 참조 문제를 런타임에서 발견 (컴파일 시점이 아닌 실행 시점에서 오류 발생)
- 객체가 스프링 컨테이너에 종속적 (@Autowired가 스프링에서만 동작하므로, 스프링 없이 테스트하기 어려움)
⚠ ⚠ 스프링 공식 문서에서도 필드 주입보다는 생성자 주입을 권장함
3. Setter 주입 (Setter Injection)
📌 개념
- Setter 메서드를 통해 의존성을 주입하는 방식
✅ 예제
@Component
public class Car {
private Engine engine;
@Autowired
public void setEngine(Engine engine) {
this.engine = engine;
}
}
🏆 장점
- 선택적 의존성 주입 가능 (@Autowired(required = false) 사용 가능)
- 유연한 변경 가능 (런타임에서 특정 의존성을 변경할 수 있음)
❌ 단점
- 불변성을 유지할 수 없음 (Setter를 통해 언제든 변경 가능)
- 객체가 완전히 생성된 후에야 의존성이 주입됨 (객체 생성 시점과 의존성 주입 시점이 다름)
어떤 방식이 가장 좋을까?
의존성 주입 방식 장점 단점 권장 여부
생성자 주입 | 불변성 유지, 순환 참조 방지 | 생성자가 길어질 수 있음 | ✅ 가장 권장됨 |
필드 주입 | 코드가 간결함 | 테스트 어려움, 순환 참조 문제 발생 가능 | ❌ 비권장 |
Setter 주입 | 선택적 주입 가능, 유연함 | 불변성 유지 어려움 | ⚠ 일부 경우에 사용 가능 |
🔥 결론: 기본적으로 생성자 주입을 사용하고, 선택적 의존성이 필요하면 Setter 주입을 활용하는 것이 좋음. 필드 주입은 추천되지 않음! 🚀
추가적으로 @RequiredArgsConstructor를 활용하면 생성자 주입을 더욱 편리하게 사용할 수 있다! 😃