SpringBoot

DI(Dependency Injection) - (3)

알파 조 2025. 4. 18. 01:04
SMALL

묵시적 DI(Implicit Dependency Injection) 

스프링에서 묵시적 DI는 개발자가 “어떤 빈을 연결할지”를 직접 코드에 쓰지 않고,
스프링 컨테이너가 자동으로 빈을 찾아 주입해 주는 방식


1. @Component: 빈 후보(Bean Candidate) 등록

  • 역할: 이 클래스는 “스프링이 관리할 수 있는 빈”이라는 표시만 한다.
  • 실제 빈 등록은 @ComponentScan이 해당 패키지를 스캔해야 일어난다.
  • 기본 빈 이름
    1. 클래스명 PascalCase → 메서드명 스타일 camelCase로 변환
      • 예: MyService → myService
    2. @Component("customName") 속성으로 직접 지정 가능
// 빈 후보 표시
@Component
public class Hat { … }       // 빈 이름: "hat"

@Component("watch")
public class Watch { … }     // 빈 이름: "watch"

2. @Autowired: 자동 주입(Autowiring)

  • 역할: 스프링이 타입(또는 이름)에 맞는 빈을 찾아서 주입해 준다.
  • 적용 대상: 생성자, 필드, 세터, 일반 메서드 파라미터 전부 가능
  • required 속성: required=true(기본)면 빈이 반드시 하나 있어야 한다.
  • 빈이 여러 개일 때
    1. 타입 중복 오류 NoUniqueBeanDefinitionException 발생
    2. @Qualifier("beanName")를 추가해 이름으로 한정 주입 가능
  • 생성자가 하나면 @Autowired 생략해도 자동으로 주입된다.
@Component
public class Bag {
  private final Hat hat;
  private Watch watch;

  // 생성자 주입: final 필드 사용 가능, 필수 의존성에 권장
  @Autowired
  public Bag(Hat hat, @Qualifier("watch") Watch watch) {
    this.hat   = hat;
    this.watch = watch;
  }

  // 선택적/추가 의존성: 세터 주입
  @Autowired(required=false)
  public void setSmartWatch(SmartWatch sw) {
    this.smartWatch = sw;
  }
}

3. @ComponentScan: 패키지 스캔

  • 역할: @Component가 붙은 클래스를 스캔해서 실제 빈으로 등록한다.
  • 사용 위치: 보통 @Configuration 클래스 위에 붙인다.
  • 스캔 대상 설정:
    • @ComponentScan("com.example.app") → 해당 패키지 및 하위 모두 스캔
    • @ComponentScan(basePackages={"pkg1","pkg2"}) 처럼 배열로 지정 가능
@Configuration
@ComponentScan("com.ssfy.live.accessory")
public class AppConfig { }

4. 전체 흐름 예시

1. 클래스 선언

@Component class Hat { /* … */ }
@Component("watch") class Watch { /* … */ }
@Component class SmartWatch { /* … */ }

 

2. 의존성 주입 클래스

@Component
public class Bag {
  private final Hat hat;
  private final Watch watch;
  private SmartWatch smartWatch;

  // 필수 의존성: 생성자 주입
  public Bag(Hat hat, Watch watch) {
    this.hat   = hat;
    this.watch = watch;
  }

  // 선택 의존성: 세터 주입
  @Autowired(required=false)
  public void setSmartWatch(SmartWatch smartWatch) {
    this.smartWatch = smartWatch;
  }
}

 

3. 컨테이너 띄위기

AnnotationConfigApplicationContext ctx =
    new AnnotationConfigApplicationContext(AppConfig.class);

Bag bag = ctx.getBean(Bag.class);
ctx.close();

5. 빈 조회 시 오류 처리

  • 문제: @Autowired Watch watch; 선언만 했는데, Watch 타입 빈이 2개(watch, smartWatch) 있어 오류
NoUniqueBeanDefinitionException: 
 expected single matching bean but found 2: smartWatch,watch
  • 해결: @Qualifier("watch")로 빈 이름을 지정한다.
@Autowired
public Bag(Hat hat,
           @Qualifier("watch") Watch watch) {
  … 
}

6. 주입 방식별 장·단점 비교

방식 장점 단점 언제 사용?

생성자 주입 final 필드 가능• 불변성 보장• 의존 누락 시 컴파일 단계 오류 파라미터 늘어나면 코드 길어짐 모든 필수 의존, 테스트·리팩토링 시 권장
세터 주입 선택적 의존성에 적합• 순환 참조 해결 가능 불변성 보장 안 됨• 누락 시 런타임 오류 Optional 의존, 순환 참조가 불가피할 때
필드 주입 코드 가장 간결 Mock 주입 어려움• 불변성 보장 안 됨 테스트 편의 목적, 아주 단순한 빈 한정 권장

7. 명시적 vs 묵시적 DI 비교

구분 명시적 DI 묵시적 DI

코드 작성 JavaConfig(@Configuration+@Bean)나 XML에 명시적으로 빈 생성·주입 코드 작성 @Component + @Autowired 애노테이션만으로 자동 연결
관심사 분리 비즈니스 로직 ↔ 빈 관리 로직 명확 분리 빈 관리 코드와 비즈니스 코드가 섞여 보일 수 있음
구조 파악 설정 클래스/파일만 보면 전체 의존관계 한눈에 파악 가능 코드 전체를 스캔해야 빈 구성을 유추해야 함
유연성 외부 라이브러리 빈 등록 자유, 세밀한 제어 가능 스캔 방식 제약 → 외부 빈 혼합 시 JavaConfig 필요할 수 있음
개발 속도 vs 안정성 안정성↑, 설정량↑ 개발 속도↑, 설정 실수로 인한 런타임 오류 위험↑

: 보통은 묵시적 DI를 기본으로 쓰되,

  • 외부 라이브러리 통합
  • 특정 조건부 빈 등록
  • 테스트용 목(Mocking) 빈 등록
    같은 특수한 경우에만 JavaConfig(@Bean) 같은 명시적 DI를 보조적으로 사용하면 좋다

 

728x90