kimgusxo 님의 블로그
1강. 제어의 역전(IoC)과 의존성 주입(DI) 본문
1. 객체지향 프로그래밍
1-1. 객체지향의 핵심 원칙 중 하나: 높은 응집도, 낮은 결합도
- 응집도: 각 클래스가 자신의 역할에 충실한가?
- 결합도: 서로 얼마나 밀접하게 연결되어 있는가?
// 높은 결합도 예시
public class OrderService {
private final PaymentService paymentService = new PaymentService();
}
- OrderService는 항상 PaymentService에 종속됨
- 테스트가 어렵고 구현체 변경이 어려운 문제가 발생
2. 제어의 역전(IoC)의 등장
- IoC(Inversion of Control)란 객체 생성과 의존성 연결을 개발자가 아니라 프레임워크(컨테이너)가 대신 처리하는 것
- 즉 "제어의 주체가 뒤바뀐다"
2-1. 기존 구조
OrderService orderService = new OrderService(new PaymentService());
- 개발자가 객체를 직접 생성함.
- 개발자가 직접 객체의 생명주기(생성~소멸)을 관리한다.
2-2. 스프링 IoC 구조
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
- IoC의 주체: ApplicationContext
- 이 컨테이너가 모든 빈을 만들고 관리하고 주입까지 한다.
- 개발자는 @Component, @Autowired 같은 어노테이션만 선언하면 됨.
4. 의존성 주입(DI)
- DI(Dependency Injection)란 객체가 필요로 하는 의존 객체를 외부(스프링 컨테이너)에서 주입해주는 방식
4-1. 의존성 주입 방식
방식 | 특징 | 권장 여부 |
생성자 주입 | 가장 명확하며 불변성을 유지한다. | 권장 |
필드 주입 | 가시성이 낮고 테스트가 어렵다 | 추천하지 않음 |
세터 주입 | 선택적 주입 가능 | 서브로 사용 |
4-2. 의존성 주입 예제
@Component
public class PaymentService {
public void pay() {
System.out.println("결제 실행");
}
}
@Component
public class OrderService {
private final PaymentService paymentService;
// 생성자 주입
@Autowired
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void order() {
paymentService.pay();
}
}
4-3. 동작 흐름
- 스프링이 실행되며 @ComponentScan으로 클래스를 탐색한다.
- PaymentService, OrderService를 생성하여 Bean으로 등록한다.
- OrderService는 생성자 호출 시 자동으로 PaymentService 주입
- 개발자는 결론적으로 context.getBean(OrderService.class)만 호출
5. 스프링이 DI를 사용하는 이유
5-1. DI의 장점
- 유지보수성: 결합도를 낮춰서 모듈 교체가 용이하다.
- 확장성: 인터페이스 기반의 개발이 가능하다.
- 테스트 용이성: Mock(가짜 객체)를 주입 가능하다.
- 클린코드 구조: 역할이 명확해진다. (OCP원칙 충족)
5-2. 스프링 철학
- "설정은 코드보다 강하다."
- 구성(Dependency)는 컨테이너가 관리함으로써 개발자는 비즈니스로직에 온전히 집중할 수 있다.
5-3. DI없이 IoC만 구현해보기
class AppConfig {
public OrderService orderService() {
return new OrderService(paymentService());
}
public PaymentService paymentService() {
return new PaymentService();
}
}
- 직접 객체를 구성하지만 구성 책임을 AppConfig가 가진다.
- 스프링도 내부적으로 이렇게 IoC 컨테이너를 구현한다.
5-4. IoC와 DI의 관계
- DI는 IoC의 구현 방식 중 하나
- 즉, 스프링은 IoC 컨테이너를 사용하여 DI를 구현하는 프레임워크이다.
6. IoC/DI 관련 핵심 어노테이션
@Component | 일반적인 빈 등록 대상 |
@Controller | 컨트롤러 계층용 빈 |
@Service | 서비스 계층용 빈 |
@Repository | DAO 계층용 빈 + 예외 변환 지원 |
@Autowired | 의존성 자동 주입 |
@Qualifier | 동일 타입 빈들 중 구체적으로 선택 |
@Bean | 자바 코드 기반 수동 빈 등록 |
7. 주의할 점
- 순환 참조(A - B - A)에 주의해야 한다.
- 의존성은 인터페이스로 주입하는 습관을 가져야한다.
- 생성자 주입은 final을 활용하여 불변성을 확보해야한다.
'BackEnd > Spring' 카테고리의 다른 글
2강. 빈(Bean)과 컴포넌트 스캔(ComponentScan) (0) | 2025.05.07 |
---|---|
0강. 스프링의 배경과 역사 (0) | 2025.05.06 |