IoC (Inversion of Control) : 제어의 역전
코드의 흐름이 제 3자에게 위임되는 것을 의미한다.
public class A {
private B b;
public A(){
b = new B();
}
}
A 클래스에서 B 필드를 가지고 있고, 생성자 내부에서 new 키워드를 사용해서 필드를 초기화하고 있다.
즉, 객체 생명주기나 메서드의 호출을 개발자가 직접 '제어' 하고 있다. 이러한 흐름을 외부에서 관리를 한다면 ?
public class A {
@Autowired
private B b;
}
B라는 객체가 스프링 컨테이너에게 관리되고 있는 Bean이라면 @Autowired를 통해 객체를 주입받을 수 있다. 개발자가 직접 객체를 관리하지 않고, 스프링 컨테이너에서 객체를 생성하여 해당 객체에 주입시켜준다.
→ 이것이 바로 제어의 역전이다. 즉, 객체의 생명주기(생성과 소멸)와 의존관계 관리의 주도권이 개발자에서 스프링 컨테이너로 넘어간 것이다.
IoC는 왜 필요할까 ?
- IoC가 없을 경우, 개발자가 직접 객체를 만들고, 필요한 다른 객체도 직접 넣어주어야한다. 그렇게 되면 클래스 간 결합도가 높아져 유지보수가 테스트가 어려워진다.
- IoC가 있는 경우, 객체 생성과 의존성 주입을 스프링 컨테이너가 대신 관리하면 개발자는 '필요하다' 라는 것만 선언하면 된다. 그렇게 되면 결합도는 낮아지고, 유연성이 높아진다.
DI (Dependency Injection) : 의존성 주입
IoC를 구현하기 위해 사용하는 디자인 패턴 중 하나로, 이름 그대로 객체의 의존관계를 외부에서 주입시키는 패턴이다. (여기서 외부라는 것은 스프링 컨테이너를 말한다.)
의존성 주입으로는 3가지 방법이 존재한다.
1) 필드 주입
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
public void processOrder(String orderId) {
paymentService.processPayment(orderId);
}
}
- 장점 : 간결하다.
- 단점 : 테스트 시 의존성을 직접 주입하기 어려우며, 객체 생성 시 의존성 설정이 명확하지 않다.
2) Setter 주입
@Service
public class OrderService {
private PaymentService paymentService;
@Autowired
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void processOrder(String orderId) {
paymentService.processPayment(orderId);
}
}
- 장점 : 선택적 의존성 처리에 적합하다.
- 단점 : 객체가 생성된 이후에도 의존성을 변경할 수 있어 불변성을 보장하지 못한다.
3) 생성자 주입 (Constructor Injection)
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void processOrder(String orderId) {
paymentService.processPayment(orderId);
}
}
- 장점
- 불변성 보장 (final 키워드를 사용해 주입된 의존성이 변경되지 않음을 명확히 할 수 있다.)
- `final` 키워드 : 반드시 필요한 의존성이라는 뜻이다. 한 번 생성자에서 값이 들어가면 다시는 바꿀 수 없다. 위의 코드에서도 OrderService는 PaymentService 없이는 만들어지지 못한다는 것을 알 수 있다.
- 테스트 용이성 (의존성을 주입받는 객체를 쉽게 Mock 객체로 대체할 수 있다.)
- 필수 의존성 명시 (생성자에 명시적으로 필요한 의존성을 선언할 수 있다.)
- 불변성 보장 (final 키워드를 사용해 주입된 의존성이 변경되지 않음을 명확히 할 수 있다.)
+ ) `@RequiredArgsConstructor` : 생성자 코드를 매번 작성하는 것이 번거로우니깐 롬북(Lombok)이 도와준다.
@Service
@RequiredArgsConstructor
public class OrderService {
private final PaymentService paymentService;
}
- `final` 필드를 보고 자동으로 생성자를 생성해준다.
- 스프링은 생성자가 하나만 있으면 `@Autowired`을 붙이지 않아도, 자동 적용한다.
'Spring' 카테고리의 다른 글
| [Spring] 영속성 컨텍스트(Persistence Context) (3) | 2025.08.11 |
|---|---|
| [Spring] ORM과 JPA(Java Persistence API) (3) | 2025.08.11 |
| [Spring] 레이어드 아키텍처(Layered Architecture)와 Spring MVC (0) | 2025.08.05 |
| [Spring] DispatcherServlet과 스프링부트 동작 구조 (3) | 2025.07.26 |
| [Spring] Spring Security의 구조와 흐름 (0) | 2025.07.17 |