[Spring] DI와 IoC 이해하기

2025. 4. 11. 01:45·Programming/Spring
목차
  1. DI 적용 전 객체 사용 방식
  2. DI (Dependency Injection)
  3. IoC (Inversion of Control)
  4. ▶︎ Spring DI 과정
  5. ▶︎ Spring DI 방식
  6. 🔗 Reference

DI 적용 전 객체 사용 방식

DI 개념을 적용하기 전에는 객체 사용 시 어떤 방식을 사용했을까?

1. 직접 객체 생성

사용하려는(=의존하려는) 객체를 new 생성자를 통해 직접 생성한다.

public class CafeController {
private StarbucksService starbucksService = new StarbucksService();
public void orderCoffee() {
starbucksService.brew();
}
}

이러한 방법은 객체간 결합도가 강하다는 단점이 있다. 만일 스타벅스가 부도가 나서 서비스를 스타벅스가 아닌 팀홀튼으로 바꿔야 할 경우 (ㅎ) CafeController 내부의 코드를 직접 수정해야 한다.

StarbucksService를 사용하고 있는 Controller가 수십개가 된다면 해당하는 Controller의 내부 코드를 일일이 전부 수정해야 한다.

2. 팩토리 패턴

객체의 생성을 전담하는 팩토리 클래스를 만들어 사용하는 방법이다.

public interface CoffeeService {
void brew();
}
public class StarbucksService implements CoffeeService {
public void brew() {
System.out.println("Starbucks 커피 제조 중...");
}
}
public class CoffeeServiceFactory {
public static CoffeeService createCoffeeService() {
return new StarbucksService();
}
}
public class CafeController {
private CoffeeService coffeeService;
public CafeController() {
this.coffeeService = CoffeeServiceFactory.createCoffeeService();
}
}

이제 Controller에서 사용하려는 구현체를 변경하고 싶을 경우, 팩토리 내 코드만 변경해주면 된다.

하지만 여전히 다음과 같은 단점을 가진다.

  • 객체가 매번 새로 생성되어 리소스가 낭비된다.
  • 객체 생명주기를 제어할 수 없다.
  • 객체를 공유할 수 없어 상태 동기화가 어렵다.

👀 객체 생명주기를 제어할 수 없다?

예를 들어 아래와 같은 팩토리 패턴이 있다고 했을 때

public class ExpensiveCacheService {
private final Cache cache = new Cache(); // 메모리 엄청 쓰는 캐시
public void close() {
cache.clear(); // 종료 시 꼭 해줘야 하는 작업
}
}

해당 클래스를 사용하고 나서는 반드시 close() 메서드를 호출하여 캐시를 정리해줘야 하지만

해당 메서드를 언제, 어디서, 어떻게 호출할 것을 강제할 수 없다.

리소스 정리 타이밍을 제어할 수 없는 것이다.


DI (Dependency Injection)

의존성 주입 : 필요한 객체(의존성)을 외부에서 주입받는 방식

위 기존 방식들의 문제점을 해결하기 위해 나온 것이 DI 개념이다.

  • 객체를 직접 생성하지 않고, 필요한 객체를 외부에서 주입해주는 방식이다.
  • 객체의 생성, 주입, 생명주기 관리까지 외부가 담당하게 된다.

// 인터페이스
public interface CoffeeService {
void brew();
}
// 구현체
public class StarbucksService implements CoffeeService {
public void brew() {
System.out.println("Starbucks 커피 제조 중...");
}
}
// 컨트롤러 (서비스를 주입받음)
public class CafeController {
private final CoffeeService coffeeService;
// 생성자 주입
public CafeController(CoffeeService coffeeService) {
this.coffeeService = coffeeService;
}
public void orderCoffee() {
coffeeService.brew();
}
}
// 실행 메인 클래스 : 의존성 주입 및 애플리케이션 메인 클래스
public class Main {
public static void main(String[] args) {
// 한 번만 생성
CoffeeService coffeeService = new StarbucksService();
// 생성자로 주입
CafeController cafeController = new CafeController(coffeeService);
RestaurantController restaurantController = new RestaurantController(coffeeService);
// 각 컨트롤러에서 동일한 CoffeeService 사용
cafeController.orderCoffee();
restaurantController.orderCoffee();
}
}
  • 객체 재사용 : 하나의 인스턴스를 여러 클래스에서 공유 가능하다.
    • 서비스 객체를 한 번만 생성하고 여러 곳에서 주입해서 사용하므로 리소스 낭비를 최소화한다.
  • 상태 일관성 유지 : 하나의 인스턴스를 여러 클래스에서 공유할 수 있으므로 내부 상태가 하나로 유지된다.
    • 재고 수량, 포인트처럼 상태를 가진 객체를 공유하면 데이터 동기화가 쉽다.
  • 유지보수성 향상 : 객체 생성은 외부에서 담당하여 해당 객체를 사용하는 클래스내 코드 변화를 최소화한다.
    • 실제 어떤 구현체를 사용할 지는 외부에서 결정하므로 외부에서 주입되는 구현체만 바꾸면 된다.

위 예제에서는 개발자가 작성한 Main 클래스가 의존성 주입을 담당하고 있다.

Spring을 사용하면 이 역할을 Spring Container가 대신한다.


IoC (Inversion of Control)

제어의 역전 : 객체의 생성과 의존성 주입을 개발자가 아닌 Spring이 대신 수행해주는 구조
👀 왜 제어의 역전이라고 할까?
기존에는 개발자가 new 로 직접 객체를 생성하였다. (각 객체 안에서든, Main 메서드 안에서든)
스프링이 알아서 객체를 만들고, 필요한 곳에 주입(DI)함으로써 객체 제어권이 개발자에서 프레임워크로 역전됨을 의미한다.

▶︎ Spring DI 과정

1. Spring Boot 어플리케이션 실행

@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
  • SpringApplicaiton.run()이 실행되면, 내부적으로 ApplicationContext가 생성된다.
  • ApplicationContext는 Spring의 핵심 컨테이너로 등록된 객체(Bean)를 담아두고 관리하는 역할을 한다.

2. 컴포넌트 스캔 및 빈 등록

  • @SpringBootApplication 어노테이션은 @ComponentScan을 포함한다.
    • 현재 패키지와 하위 패키지를 스캔하여 빈으로 등록하기 위한 어노테이션이 붙어있는 클래스들을 찾아 빈으로 등록한다.
    • @Component, @Service, @Controller 등
  • @Configuration 클래스에 정의된 @Bean 메서드들도 스캔되어 빈으로 등록된다.
    • @Bean 어노테이션이 붙은 메서드를 찾아 실행 후 리턴된 객체를 빈으로 등록한다.

3. 의존성 분석 (DI 준비)

@RequiredArgsConstructor
@RestController
public class CafeController {
private final CoffeeService coffeeService;
}
  • 등록된 빈 간의 의존 관계를 분석한다.
  • 예를 들어 CafeController를 만들기 위해선 CoffeeService 타입의 Bean이 필요하다는 것을 파악한다.
  • 실제 등록된 Bean 중 CoffeeService를 구현한 StarbucksService가 있으므로 StarbucksService를 주입 대상으로 결정한다.

4. 의존성 주입 실행 (DI)

  • 의존성 주입이 필요한 빈에 해당 빈을 주입한다.

▶︎ Spring DI 방식

1. 생성자 주입 (가장 권장됨)

필요한 의존성을 생성자를 통해 받아오는 방식이다.

@Service
public class StarbucksService implements CoffeeService {
public void brew() {
System.out.println("Starbucks 커피 제조 중...");
}
}
@RestController
@RequiredArgsConstructor // 생성자 자동 생성
public class CafeController {
private final CoffeeService coffeeService;
@GetMapping("/order")
public void orderCoffee() {
coffeeService.brew();
}
}
  • 생성자가 하나만 있으면 @Autowired 어노테이션 없이 생성자만으로 주입이 가능하다.
    • 생성자가 여러 개가 있다면? 주입 받을 생성자에 @Autowired를 명시해줘야 한다.
  • 가장 권장되는 이유?
    • 필드를 final로 설정할 수 있다 → 불변 객체 설계가 용이하다.
    • 테스트 시 mock 객체를 주입하기 쉽다 → new 생성자 안에 넣어주면 됨
    • 순환 참조를 컴파일 단계에서 감지 가능하다.

2. setter 주입

객체를 먼저 만들고 나중에 @Autowired가 붙은 세터 메서드로 주입하는 방식이다.

@Service
public class StarbucksService implements CoffeeService {
public void brew() {
System.out.println("Starbucks 커피 제조 중...");
}
}
@RestController
public class CafeController {
private CoffeeService coffeeService;
@Autowired
public void setCoffeeService(CoffeeService coffeeService) {
this.coffeeService = coffeeService;
}
@GetMapping("/order")
public void orderCoffee() {
coffeeService.brew();
}
}
  • 의존성은 setCoffeeService()가 호출되어야만 주입된다.
  • 만약 이 호출이 누락되면 coffeeService는 null인 상태로 참조될 수 있어 NPE 위험성이 존재한다.
  • 선택적으로 의존해야하거나 초기화 시점이 중요한 경우 사용한다.
    • 선택적으로 의존해야 하는 경우? 예를 들어 NotificationService에서 EmailSender, SmsSender 등을 의존하고 상황에 따라 선택적으로 의존성을 주입받고 싶을 때

3. 필드 주입 (권장되지 않음)

필드에 바로 @Autowired를 붙여 의존성을 주입하는 방식이다.

내부적으로 Reflection API를 통해 private 필드를 강제로 열어서 인스턴스를 주입한다.
@Service
public class StarbucksService implements CoffeeService {
public void brew() {
System.out.println("Starbucks 커피 제조 중...");
}
}
@RestController
public class CafeController {
@Autowired
private CoffeeService coffeeService;
@GetMapping("/order")
public void orderCoffee() {
coffeeService.brew();
}
}
  • 가장 권장되지 않는 이유?
    • 테스트 시 mock 객체를 주입하기 위해서 Reflection을 사용해야 한다.
    •  어노테이션이 없으면 Bean이 생성되지 않음 -> 프레임워크 종속성이 너무 커진다.

🔗 Reference

  • Spring) DI(의존성 주입) 그리고 IoC(제어 역전)
  • [Spring] IoC와 DI 알아보기
  • Introduction to the Spring IoC Container and Beans
저작자표시 비영리 변경금지 (새창열림)

'Programming > Spring' 카테고리의 다른 글

[SpringBoot] 파일 업로드를 위한 MultipartFile의 처리/동작 방식  (1) 2025.04.09
[Spring] JPA Cascade에 대해 정리해보자 💭  (0) 2025.03.11
[Spring] 영속성 컨텍스트에 대해 정리해보자  (0) 2025.02.27
[Spring] Interceptor에 대해 알아보자  (0) 2025.02.26
[Spring] LoginCheckFilter 구현 : 로그인/아웃 로직 구현하기  (0) 2025.02.10
  1. DI 적용 전 객체 사용 방식
  2. DI (Dependency Injection)
  3. IoC (Inversion of Control)
  4. ▶︎ Spring DI 과정
  5. ▶︎ Spring DI 방식
  6. 🔗 Reference
'Programming/Spring' 카테고리의 다른 글
  • [SpringBoot] 파일 업로드를 위한 MultipartFile의 처리/동작 방식
  • [Spring] JPA Cascade에 대해 정리해보자 💭
  • [Spring] 영속성 컨텍스트에 대해 정리해보자
  • [Spring] Interceptor에 대해 알아보자
다섯자두
다섯자두
All I need is 💻 , ☕️ and a dash of luck
subbniAll I need is 💻 , ☕️ and a dash of luck
  • 다섯자두
    subbni
    다섯자두
  • 전체
    오늘
    어제
    • 분류 전체보기 (91)
      • Programming (54)
        • CS (0)
        • Network (3)
        • Database (5)
        • Java (13)
        • Javascript (0)
        • React (18)
        • Spring (8)
        • Cloud (1)
        • ETC (6)
        • 파고들기 (0)
      • Project (13)
        • FromBookToBook (5)
        • Spring (5)
        • Node.js & React (3)
      • Algorithm (20)
        • Study (7)
        • 자료구조 (7)
        • 문제 풀이 (5)
      • TroubleShooting (4)
      • 회고 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • velog
  • 인기 글

  • 태그

    HTTP
    redis
    로그인
    프로젝트
    SQS
    springboot
    Til
    network
    자료구조
    알고리즘
    실시간 데이터 전송 기술
    SSE
    Spring
    pdf 프리뷰 실패
    알림 기능
    aws
    최단거리
    outbox 패턴
    티스토리챌린지
    SQL
    pdf 자동 다운로드
    java
    오블완
    outbox
    코딩테스트
    mysql
    Database
    Express
    재시도 로직
    JPA
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
다섯자두
[Spring] DI와 IoC 이해하기

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.