[Spring] DI

배창민·2025년 10월 14일
post-thumbnail

스프링 DI(Dependency Injection)

스프링 컨테이너가 의존 관계자동 연결해 객체 간 결합도를 낮추는 핵심 메커니즘


1) DI 핵심 개념

  • 정의: 객체가 사용할 의존 객체를 직접 생성하지 않고, 컨테이너가 주입한다.
  • 효과: 구체 클래스 의존을 줄여 유지보수성·테스트 용이성·확장성 향상.
  • 포인트: 필드는 상위 타입(인터페이스/추상) 으로 선언하고, 구현체는 주입 시점에 결정.
// Before: 강결합
class A {
  private B b = new B();   // A가 직접 생성 → B 교체 시 A 수정 필요
}

// After: 느슨한 결합
class A {
  private B b;             // 상위 타입 의존
  public A(B b) { this.b = b; }   // 외부 주입(생성자)
}
interface B {}
class NewB implements B {}

2) 예제 도메인

  • Account(인터페이스) ← PersonalAccount(구현체)
  • MemberDTOAccount의존
public interface Account {
  String getBalance();
  String deposit(int money);
  String withDraw(int money);
}

@RequiredArgsConstructor
public class PersonalAccount implements Account {
  private final int bankCode;
  private final String accNo;
  private int balance;
  // ... 메서드 생략
}

@Getter @Setter @ToString
@AllArgsConstructor @NoArgsConstructor
public class MemberDTO {
  private int sequence;
  private String name;
  private String phone;
  private String email;
  private Account personalAccount;  // 상위 타입 의존
}

3) XML 설정으로 DI

3-1) 생성자 주입

<!-- 구현체로 등록해야 함 (interface X) -->
<bean id="account" class="com.ohgiraffers.common.PersonalAccount">
  <constructor-arg index="0" value="20"/>
  <constructor-arg index="1" value="110-234-567890"/>
</bean>

<bean id="member" class="com.ohgiraffers.common.MemberDTO">
  <constructor-arg name="sequence" value="1"/>
  <constructor-arg name="name" value="홍길동"/>
  <constructor-arg name="phone" value="010-1234-5678"/>
  <constructor-arg name="email" value="hong123@gmail.com"/>
  <constructor-arg name="personalAccount">
    <ref bean="account"/>
  </constructor-arg>
</bean>

3-2) 세터 주입

<bean id="account" class="com.ohgiraffers.common.PersonalAccount">
  <constructor-arg index="0" value="20"/>
  <constructor-arg index="1" value="110-234-567890"/>
</bean>

<bean id="member" class="com.ohgiraffers.common.MemberDTO">
  <property name="sequence" value="1"/>
  <property name="name" value="홍길동"/>
  <property name="phone" value="010-1234-5678"/>
  <property name="email" value="hong123@gmail.com"/>
  <property name="personalAccount" ref="account"/>
</bean>
  • constructor-arg vs property 만 다를 뿐 동작 결과는 동일

4) 자바 설정으로 DI

4-1) 생성자 주입

@Configuration
public class ContextConfiguration {

  @Bean
  public Account accountGenerator() {
    return new PersonalAccount(20, "110-234-567890");
  }

  @Bean
  public MemberDTO memberGenerator() {
    return new MemberDTO(
      1, "홍길동", "010-1234-5678", "hong123@gmail.com",
      accountGenerator()   // 빈 참조 전달
    );
  }
}

4-2) 세터 주입

@Configuration
public class ContextConfiguration {

  @Bean
  public Account accountGenerator() {
    return new PersonalAccount(20, "110-234-567890");
  }

  @Bean
  public MemberDTO memberGenerator() {
    MemberDTO m = new MemberDTO();
    m.setSequence(1);
    m.setName("홍길동");
    m.setPhone("010-1234-5678");
    m.setEmail("hong123@gmail.com");
    m.setPersonalAccount(accountGenerator());  // 빈 참조 전달
    return m;
  }
}

5) 생성자 vs 세터 주입 선택 가이드

  • 생성자 주입

    • 필수 의존성 보장, 불변 설계 용이, 순환 참조 조기 감지
  • 세터 주입

    • 선택적 의존성, 재설정 가능, 테스트에서 대체/수정 용이

실무에선 핵심 의존성 = 생성자, 선택 옵션 = 세터 패턴이 안전


6) 체크리스트

  • 필드는 인터페이스에 의존하고, 구현체는 빈으로 주입
  • 빈 등록 시 구현 클래스로 등록
  • 생성자 주입 기본, 필요 시 세터 보완
  • XML·자바 설정 모두 빈 참조(Ref/메서드 호출 반환) 로 연결
  • DI로 결합도를 낮추고 테스트/교체/확장이 쉬운 구조 유지
profile
개발자 희망자

0개의 댓글