관리 메뉴

kimgusxo 님의 블로그

1강. 제어의 역전(IoC)과 의존성 주입(DI) 본문

BackEnd/Spring

1강. 제어의 역전(IoC)과 의존성 주입(DI)

kimgusxo 2025. 5. 7. 16:24

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. 동작 흐름

  1. 스프링이 실행되며 @ComponentScan으로 클래스를 탐색한다.
  2. PaymentService, OrderService를 생성하여 Bean으로 등록한다.
  3. OrderService는 생성자 호출 시 자동으로 PaymentService 주입
  4. 개발자는 결론적으로 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