[스프링] 의존관계 자동 주입에 대하여

2025. 6. 26. 21:09·스프링

스프링 프레임워크는 객체 간의 의존성을 개발자가 직접 연결하지 않아도, 자동으로 주입해주는 기능을 제공한다. 이를 의존관계 자동 주입(Dependency Injection)이라고 하며, 스프링 컨테이너가 대신 객체를 생성하고 필요한 곳에 주입해준다.

 

스프링에서는 의존관계를 주입하는 방식이 여러 가지가 있으며, 상황에 따라 그 특성과 장단점이 존재한다. 이번 글에서는 의존관계 자동 주입 방식의 종류와 각각의 특징을 정리하고, 스프링이 권장하는 방식인 생성자 주입에 대해 중점적으로 살펴본다.

 

의존관계 주입 방식 4가지

의존관계를 주입하는 방식은 크게 네 가지가 있다.

  1. 생성자 주입
  2. 수정자 주입 (Setter 주입)
  3. 필드 주입
  4. 일반 메서드 주입

 

1. 생성자 주입

가장 일반적이며 스프링이 권장하는 방식이다.
생성자를 통해 의존 객체를 주입받는 구조로, 스프링이 객체를 생성할 때 생성자에 필요한 인자를 자동으로 넣어준다.

@Component
public class OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderService(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

 

특징

  • 생성자 호출 시점에 딱 1번만 호출되므로, 이후 값이 변하지 않는다.
  • 필드에 final 키워드를 사용할 수 있어, 컴파일 시점에 누락 여부를 확인할 수 있다.
  • 생성자가 하나만 있으면 @Autowired를 생략해도 자동 주입된다.
  • 테스트 작성과 유지보수에 유리하다.

 

2. 수정자 주입 (Setter 주입)

Setter 메서드를 통해 의존 객체를 주입하는 방식이다.
선택적 주입이 필요한 경우에 유용하다.

@Component
public class OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }
}

 

특징

  • 선택적, 변경 가능한 의존관계에 적합하다.
  • Setter 메서드가 public으로 열려 있어야 하므로, 외부에서 실수로 값을 변경할 위험이 있다.
  • 주입할 대상이 없을 때 오류가 발생하지 않도록 하고 싶다면 @Autowired(required = false)로 설정할 수 있다.

 

3. 필드 주입

가장 코드가 간결하지만, 권장되지 않는 방식이다.
@Autowired를 필드에 직접 붙여서 주입하는 방식이다.

@Component
public class OrderService {

    @Autowired
    private MemberRepository memberRepository;

    @Autowired
    private DiscountPolicy discountPolicy;
}

 

특징

  • 간단하지만 외부에서 값을 변경하거나 주입하기 어렵다.
  • 테스트 코드 작성이 어렵고, DI 컨테이너가 없으면 직접 객체를 넣을 수 없다.
  • 실제 애플리케이션보다는 테스트 코드, 설정 클래스 등에서 제한적으로 사용하는 것이 좋다.

 

4. 일반 메서드 주입

일반 메서드를 통해 여러 의존 객체를 동시에 주입받을 수 있다.

@Component
public class OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

 

특징

  • 한 번에 여러 필드를 주입할 수 있다.
  • 일반적으로 자주 사용되지는 않는다.

 

생성자 주입을 권장하는 이유

최근 스프링을 비롯한 대부분의 DI 프레임워크는 생성자 주입을 적극 권장한다. 그 이유는 다음과 같다.

 

불변성을 보장한다

대부분의 의존관계는 애플리케이션 시작 시 주입되고, 이후에는 변경되지 않는 것이 일반적이다.
생성자 주입은 객체 생성 이후 의존관계를 변경할 수 없으므로 불변 객체를 만들 수 있다.
또한, 필드에 final 키워드를 사용하면 주입 누락도 컴파일 시점에 잡아낼 수 있다.

 

의존관계 누락을 방지한다

생성자 주입은 의존 객체가 없으면 객체 생성 자체가 되지 않기 때문에, 필수 의존성이 빠지는 일을 방지할 수 있다.
반면, 수정자 주입은 주입하지 않아도 오류가 나지 않기 때문에 런타임 오류로 이어질 가능성이 있다.

 

테스트에 유리하다

생성자 주입은 순수 자바 코드에서도 의존 객체를 명확하게 전달할 수 있기 때문에, DI 프레임워크 없이도 테스트가 가능하다.

 

현재 트렌드인 롬복을 이용한 생성자 자동 생성

생성자 주입을 자주 사용하다 보면 생성자 코드가 반복되는 것을 느끼게 된다. 이때 롬복(Lombok)의 @RequiredArgsConstructor를 사용하면, final이 붙은 필드만 자동으로 생성자에 포함시켜준다.

@RequiredArgsConstructor
@Component
public class OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
}

 

위 코드는 롬복이 생성자를 자동 생성해주기 때문에, 보다 간결하게 작성할 수 있다

 

 

의존 주입 충돌 해결: @Autowired 옵션들

의존 주입 중 같은 타입의 빈이 여러 개 있을 경우, 어떤 빈을 주입해야 할지 모호성이 발생한다.
이를 해결하기 위한 몇 가지 방법이 있다.

 

@Qualifier

빈의 이름을 명시적으로 지정해 원하는 구현체를 주입받을 수 있다.

@Autowired
@Qualifier("fixDiscountPolicy")
private DiscountPolicy discountPolicy;

 

@Primary

특정 빈을 기본 우선순위로 설정할 수 있다. 여러 빈 중 기본으로 선택되게 만들고 싶을 때 사용한다.

@Primary
@Component
public class RateDiscountPolicy implements DiscountPolicy {
}

 

@Autowired(required = false)

의존 객체가 없는 경우 오류 없이 무시하게 만들고 싶을 때 사용한다.

@Autowired(required = false)
public void setOptionalDependency(OptionalBean bean) {
    this.bean = bean;
}

 

결론적으로

의존관계 자동 주입은 스프링 DI의 핵심 기능이며, 다양한 방식이 존재한다.
이 중에서 생성자 주입은 불변성과 안정성, 테스트 용이성 측면에서 가장 강력한 방식이다.
또한, 롬복을 활용하면 간결하게 유지하면서도 생성자 주입의 장점을 그대로 살릴 수 있다.

상황에 따라 수정자 주입이나 필드 주입이 필요한 경우도 있지만,
실제 서비스 로직에서는 생성자 주입을 기본값으로 삼는 것이 좋은 설계 습관이다.

 

'스프링' 카테고리의 다른 글

[스프링] 빈 스코프  (1) 2025.07.24
[스프링] 빈 생명주기 콜백  (3) 2025.07.24
[스프링] 싱글톤과 스프링 컨테이너에 대하여  (3) 2025.06.26
[스프링] 스프링 컨테이너와 빈(Bean)은 왜 필요한가?  (0) 2025.06.26
[스프링] 스프링이란 무엇일까?  (0) 2025.06.26
'스프링' 카테고리의 다른 글
  • [스프링] 빈 스코프
  • [스프링] 빈 생명주기 콜백
  • [스프링] 싱글톤과 스프링 컨테이너에 대하여
  • [스프링] 스프링 컨테이너와 빈(Bean)은 왜 필요한가?
0kingki_
0kingki_
자바 + 스프링 웹 개발
  • 0kingki_
    0kingki_
    0kingki_
  • 전체
    오늘
    어제
    • 분류 전체보기 (134)
      • 코딩 테스트 (54)
      • 자바 (21)
      • 스프링 (27)
      • 타임리프 (16)
      • 스프링 데이터 JPA (8)
      • 최적화 (2)
      • QueryDSL (4)
      • AWS (2)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    BFS
    코딩테스트
    타임리프
    LocalDateTime
    예외처리
    fetch join
    SpringDataJpa
    SOLID
    백준
    재귀
    mvc
    코딩 테스트
    QueryDSL
    spring
    자바
    다형성
    Java
    JPA
    thymeleaf
    불변객체
    객체지향
    예외 처리
    스프링 데이터 JPA
    쿼리
    스프링 컨테이너
    최적화
    dfs
    컬렉션
    쿼리dsl
    스프링
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
0kingki_
[스프링] 의존관계 자동 주입에 대하여
상단으로

티스토리툴바