[Spring] Why - 스프링(Spring)에서 필드 주입이 권장되지 않는 이유.

하쮸·2025년 5월 7일
0

Error 또는 Why & How

목록 보기
19/43
post-thumbnail

1. 필드 주입이 권장되지 않는 이유.

  • 인텔리제이에서 @Autowired가 붙은 필드에 대해 “필드 주입은 권장되지 않습니다(Field injection is not recommended)”라는 문구를 종종 목격할 수 있음.

2. 의존성 주입 (Dependency Injection)

  • 의존성 주입은 객체가 직접 의존 객체를 정의하거나 만들지 않고 사용하는 방식.
    • 스프링(Spring) 프레임워크의 핵심 기능 중 하나.
  • 스프링(Spring)에서는 아래 세 가지 방법으로 의존성을 주입할 수 있음.
    • 생성자 주입(Constructor injection)
    • 세터 주입(Setter injection)
    • 필드 주입(Field injection)
  • 필드 주입@Autowired를 사용하여 클래스 내부의 필드에 직접 주입하는 방식이며, 가장 간단하지만 여러 가지 문제점이 있음.
    • 실제로 스프링(Spring) 공식 문서에서도 이제는 필드 주입을 DI(Dependency Injection) 옵션으로 제공하지 않음.

3. Null-안정성 (Null-Safety)

  • 필드 주입은 의존성이 제대로 초기화되지 않으면 NullPointerException이 발생할 수 있는 위험이 있음.

Ex) EmailService 클래스에 EmailValidator를 필드 주입.

@Service
public class EmailService {
    @Autowired
    private EmailValidator emailValidator;
}
public void process(String email) {
    if (!emailValidator.isValid(email)) {
        throw new IllegalArgumentException("INVALID_EMAIL");
    }
}
  • EmailService는 EmailValidator가 주입되어야만 제대로 동작함.
EmailService emailService = new EmailService();
emailService.process("test@naver.com");
  • 하지만 위 코드처럼 직접 객체를 생성해버리면 의존성이 주입되지 않기 때문에 NullPointerException이 발생함.
private final EmailValidator emailValidator;

public EmailService(EmailValidator emailValidator) {
    this.emailValidator = emailValidator;
}
  • 이렇게 생성자를 활용해서 주입하면 객체 생성 시 반드시 의존성을 제공해야 하므로, NullPointerException의 위험을 줄일 수 있음.
    • 필수 의존성을 외부에 공개적으로 노출시킴.
      그리고 필수 의존성을 반드시 제공해야 하므로 EmailValidator 없이 EmailService를 생성할 수 있는 방법이 없음.

4. 불변성 (Immutability)

  • 필드 주입을 사용하면 불변 객체(immutable object)를 만들 수 없음.

    • final 필드는 선언 시 또는 생성자를 통해 초기화 해야하는데
      스프링(Spring)은 생성자 호출 이후에 필드 주입을 수행하므로, final 필드에 주입할 수 없음.
  • 의존성이 변경 가능, 가변적(mutable)이기 때문에, 초기화된 후에 변경되지 않을 것이라는 보장이 없음.

    • 또한 final이 아닌 필드의 재할당은 애플리케이션 실행 중에 예기치 않은 부작용을 유발할 수 있음.
  • 권장 방식.

    • 필수 의존성은 생성자로 주입.
    • 선택적 의존성은 세터로 주입.

5. 설계상의 문제


5-1. 단일 책임 원칙 위반 (SRP, Single Responsibility Principle)

  • 객체지향 SOLID 원칙 중 단일 책임 원칙(SRP)은 각 클래스가 하나의 책임만 가져야 한다고 명시함.
    • 즉, 하나의 클래스는 하나의 작업만 담당해야 하며, 변경의 이유가 하나만 있어야 함.
  • 만약 필드 주입을 사용하면 필요 이상으로 많은 의존성들을 쉽게 추가할 수 있어서 클래스가 여러 역할을 하게 될 가능성이 있음.
    • 반면, 생성자 주입을 사용할 경우 생성자 파라미터 수가 많아지면 IDE에서 설계에 문제가 있음이라는 경고를 주므로, 의존성이 과도하다는 신호를 받을 수 있음.
      (생성자 매개변수가 7개 이상일 경우 경고를 표시)

5-2. 순환 의존성

  • 순환 의존성은 두 개 이상의 클래스가 서로를 의존할 때 발생하고 이러한 의존성 때문에 객체를 생성할 수 없음.
    • 이로 인해 런타임 오류나 무한 루프를 발생시킬 수 있음.
  • 필드 주입을 사용하면, 스프링(Spring)은 런타임 전까지 순환 의존을 감지하지 못함.
@Component
public class DependencyA {

   @Autowired
   private DependencyB dependencyB;
}

@Component
public class DependencyB {

   @Autowired
   private DependencyA dependencyA;
}
  • 필드 주입은 의존성이 필요할 때 주입되기 때문에, 스프링(Spring)은 BeanCurrentlyInCreationException을 발생시키지 않음.

    • 반면, 생성자 주입을 사용할 경우 컴파일 시점에 순환 의존성을 감지할 수 있음.
  • 또한 코드에 순환 의존성이 있다면 이는 설계에 문제가 있다는 신호일 수 있음.

    • 따라서 애플리케이션을 재설계하는 것을 고려해야 함.
  • 스프링 부트 2.6 버전부터는 순환 의존성이 기본적으로 허용되지 않음.


6. 테스트

  • 단위 테스트는 필드 주입 방식의 주요 단점을 드러냄.
EmailValidator validator = Mockito.mock(EmailValidator.class);
EmailService emailService = new EmailService();
    • EmailService 클래스의 process() 메서드가 제대로 작동하는지 테스트하려고 한다고 가정.
    • 이때 EmailValidator 객체를 모킹(mocking)해서 사용하고 싶은데, 이 객체가 필드 주입 방식으로 주입되어 있어서 테스트 코드에서 모킹된 객체로 직접 교체할 수 없음.
  • 이를 해결하기 위해 EmailService 클래스에 setter 메서드를 만들어주는 방법도 있지만, 이 방식은 테스트 외에 다른 클래스에서도 해당 메서드를 호출할 수 있어서 보안상 좋지 않음.
@Mock
private EmailValidator emailValidator;

@InjectMocks
private EmailService emailService;

@BeforeEach
public void setup() {
   MockitoAnnotations.openMocks(this);
}
  • 대신 리플렉션(Reflection)을 통해 클래스를 인스턴스화, 즉 객체를 초기화 할 수 있음.
  • 이렇게 하면 Mockito가 @InjectMocks 에노테이션을 사용해 필요한 의존성을 자동으로 주입해주려 시도함.
    하지만 필드 주입에 실패해도 Mockito는 오류를 알려주지 않음.
private EmailValidator emailValidator;
private EmailService emailService;

@BeforeEach
public void setup() {
   this.emailValidator = Mockito.mock(EmailValidator.class);
   this.emailService = new EmailService(emailValidator);
}
  • 반면 생성자 주입(constructor injection)을 사용하면 리플렉션(Reflection) 없이 필요한 의존성(모킹된 의존성)을 직접 주입할 수 있어서 테스트가 훨씬 간단해짐.
    • 테스트 측면에서는 생성자 주입이 훨씬 유리한 구조를 제공.

7. 참고.

profile
Every cloud has a silver lining.

0개의 댓글