본문 바로가기

Spring

[Spring] 스프링의 다양한 의존관계 주입 방식 (+ 생성자 주입을 권장하는 이유)

스프링 프레임워크에서 의존 관계 주입(Dependency Injection, DI)이란 스프링 컨테이너에서 객체 빈(Spring bean)을 생성해 두고, 생성한 객체를 필요한 객체에 주입하는 방식을 말합니다. 객체 자체가 코드 상에서 객체 생성에 관여하지 않아도 되기 때문에 객체 사이의 의존성을 낮출 수 있습니다. 의존 관계 주입을 사용하면 유연하고 확장성이 뛰어난 코드가 되는 것이죠.

 

스프링의 의존 관계 주입 방법에는 크게 세 가지 방법이 있습니다.

  • 필드 주입
  • 수정자 주입 (setter 주입)
  • 생성자 주입

 

오늘은 이 세 방법에 대해 알아보고, 의존 관계 주입 시 생성자 주입을 권장하는 이유를 알아보겠습니다.

 

 

필드 주입

 

@Service
public class MyService {
  @Autowired
  private MyRepository myRepository;
}

 

필드에 @Autowired 애너테이션을 사용하여 의존성을 주입하는 방법입니다. 코드가 간결하지만, 테스트 등에서 의존 관계 주입이 어려울 수 있고, 객체의 불변성을 해칠 수 있어 권장되지 않는 방법입니다.

 

 

수정자(setter) 주입

 

@Service
public class MyService {
  private MyRepository myRepository;

  @Autowired
  public void setRepository(MyRepository myRepository) {
    this.myRepository = myRepository;
  }
}

 

setter 메서드를 통해 의존 관계를 주입하는 방법입니다. 선택적으로 주입하거나, 변경 가능성이 있는 의존 관계에 주로 사용됩니다.

 

 

생성자 주입

 

@Service
public class MyService {
  private final MyRepository myRepository;

  @Autowired
  public MyService(MyRepository myRepository) {
    this.myRepository = myRepository;
  }
}

 

클래스의 생성자를 통해 의존성을 주입합니다. 때문에 생성자 호출 시점에 딱 한 번만 의존 관계가 주입되는 것이 보장되죠. 클래스의 필수적인 의존성을 생성자를 통해 받아오도록 해서 객체의 불변성을 유지하고, 컴파일 타임에 의존성 체크가 가능합니다.

 

생성자 주입 방법도 위 두 방법과 마찬가지로 @Autowired 애너테이션을 생성자 위에 붙여야 하는데요, 스프링 4.3 부터는 생성자가 하나만 있는 경우 @Autowired 애너테이션 생략이 가능합니다. 따라서 @RequiredArgsConstructor 애너테이션과 함께 쓰면 코드가 간결해집니다. @RequiredArgsConstructor 애너테이션은 final 로 선언된 클래스의 필드들을 초기화하는 생성자를 자동으로 생성해 주거든요.

 

@Service
@RequiredArgsConstructor
public class MyService {
  private final MyRepository myRepository;

  /**
  @Autowired  // 생략 가능
  // 생성자도 @RequiredArgsConstructor가 자동 생성
  public MyService(MyRepository myRepository) {
    this.myRepository = myRepository;
  }
  */
}

 

 

생성자 주입을 선택해야 하는 이유

 

최근 스프링 프레임워크를 포함한 대부분의 DI 프레임워크들이 생성자 주입을 권장하고 있습니다. IntelliJ 와 같은 IDE 에서 @Autowired 를 입력해도 필드 주입 방식을 추천하지 않는다고 메시지가 뜰 정도입니다. 생성자 주입 방식을 선택해야 하는 이유는 다음과 같습니다.

 

 

의존성 주입의 명시성 (final 키워드)

 

생성자 주입 방식에서만 필드에 final 키워드를 사용할 수 있습니다. 필드 주입, 수정자 주입 방식은 모두 생성자가 호출된 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없습니다.

 

만약 생성자에서 필드 값을 세팅하는 부분이 누락되었다면 컴파일 시점에 체크할 수 있습니다.

@Autowired 를 사용하면 해당 필드에 대한 의존성이 주입되지 않은 경우에도 스프링 빈으로 등록되어 애플리케이션이 실행될 가능성이 있습니다.

 

 

불변성

 

생성자 주입을 사용하면 의존성이 불변하게 됩니다. 한 번 생성된 객체의 상태를 변경할 수 없기 때문에 안정적인 동작을 기대할 수 있습니다.

 

대부분의 의존 관계 주입은 한 번 일어나면 애플리케이션 종료 시점까지 변경할 일이 없습니다. 오히려 대부분의 의존 관계는 불변이어야 합니다.

 

 

테스트 용이성

 

생성자 주입을 사용하면 단위 테스트 및 통합 테스트 작성이 더 쉬워집니다. 객체를 생성할 때 의존성을 주입하기 때문에 의존성을 쉽게 mock 객체로 대체할 수 있습니다.

 

 

순환 참조 방지

 

생성자 주입 방식을 통해 순환 의존성 문제를 처리할 수 있습니다. 필드 주입 방식이나 setter 주입 방식에서는 객체 생성 시점에 순환 참조 여부를 알 수 없습니다. 두 방식 모두 애플리케이션 구동 시점에는 필요한 의존성이 없는 경우 null 상태로 유지하고, 실제로 사용하는 시점에 해당 의존성을 주입하기 때문입니다.

 

참고로 스프링 부트 2.6.x 버전부터는 필드 주입 방식이나 setter 주입 방식에서도 순환 참조 여부를 캐치할 수 있다고 합니다 ^^.

 

 

 

이렇게 의존 관계 주입 시 생성자 주입 방식을 택해서 생성자 주입 방식이 주는 이점을 잘 써먹어야겠습니다.^^^~~

 

 

 

 

 

 

Reference

https://www.baeldung.com/spring-autowire

https://www.springcloud.io/post/2022-02/spring-cyclic-dependencies/#gsc.tab=0

김영한 스프링핵심원리 - 기본편

'Spring' 카테고리의 다른 글

[Spring] RestTemplate의 Error Handling  (1) 2023.06.15