들어가며

  • 이전 파트인 컴포넌트 스캔의 경우에는 스프링 프레임워크에서 어떻게 각 클래스들을 스프링 빈으로 자동으로 등록하하는지에 대해 알아보았다.
  • 이번 파트에서는 @Configuration에서 직접 수행하는 방식의 수동 의존관계 주입이 아니라 자동으로 DI를 하는 의존관계 자동 주입을 스프링 프레임워크에서 어떻게 수행하는지에 대해 알아본다.
  • 만약 DI의 개념이 혼동스럽거나 기억나지 않는다면 DI의 개념 포스트나 다른 곳에서 DI의 개념을 확실히 익히고 보는 것을 추천한다.
 

Spring Framework - DI의 개념 - 2. 다형성의 한계

돌아보기 1에서는 SOLID 원칙과 다형성에 대해서 잠시 언급했었다. 문제는 다형성을 사용하는것 만으로는 SOLID를 모두 지키면서 구현이 매우 힘들다는 점이다. 이번 포스트에서는 다형성의 한계

sehun5515.tistory.com

 

 

 

의존관계 주입 방법

  • 의존관계 주입 방법은 크게 4가지로 나눌 수 있다.
    • 생성자 주입(Constructor Injection)
    • 수정자 주입(Setter Injection)
    • 필드 주입 (Field Injection)
    • 일반 메서드 주입(Method Injection)
  • 각각의 방식을 살펴보고 특징을 알아보도록 해보자.

생성자 주입(Constructor Injection)

  • 생성자 주입은 말 그대로 생성자를 통해 객체를 외부에서 주입받는 방식이다.
  • 이를 구현한 코드는 다음과 같다.
@Component
public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
    
    @Autowired
    OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
    
    ...
}
  • @Component를 클래스 위에 달아 컴포넌트 스캔의 대상이 되게 하였다. 이제 이 클래스는 스프링 컨테이너의 관리를 받으며 생성될 때 @Autowired 를 통해 자동적으로 필요한 객체들을 주입받는다.
  • 이 과정은 객체가 "생성될 때" 수행된다. 그리고 다시는 해당 생성자가 호출되지 않으므로(스프링 컨테이너에서 해당 객체를 싱글톤으로 관리하고 있다는 사실을 잊지 말도록 하자.) 불변성이 만족된다.
  • 따라서 꼭 필요한 의존관계나 한 번 설정되고 다시 변경되면 안되는 의존관계에 사용된다.
  • 만약 생성자가 딱 하나만 존재하는게 보장되면 @Autowired는 생략해도 무방하다.
@Component
public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
    
    OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
    
    ...
}
  • 생성자 주입은 위의 특성으로 인하여 현재 스프링 공식 문서에서도 사용을 권장하는 방식이다.

수정자 주입(Setter Injection)

  • setter라고 하는 필드의 값을 변경하는 수정자 메서드를 통해 의존관계를 주입하는 방식이다.
@Component
public class OrderServiceImpl implements 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 주입 방식은 보통 주입할 스프링 빈을 선택해야 하는 상황이거나, 도중에 다른 스프링 빈으로 변경해야 할 때 사용한다.
  • Setter Injection의 경우 결국 Setter가 외부에 대해 public으로 열려 있기 때문에 자칫 잘못해서 Setter가 실행되어 변경될 위험성이 존재한다. (Constructor Injection의 경우, final 키워드를 통해 불변성을 보장받으나 Setter에서는 불가능하다. 객체가 생성된 이후에 DI가 수행되기 때문이다.)

필드 주입(Field Injection)

  • 말 그대로 필드에 바로 의존관계 주입을 수행하는 방식이다.
@Component
public class OrderServiceImpl implements OrderService{
    @Autowired
    private MemberRepository memberRepository;
    
    @Autowired
    private DiscountPolicy discountPolicy;
    
    ...
}
  • 코드 길이로만 보면 가장 간편하고 편리해 보인다.
  • 하지만 이렇게 수행할 경우에 치명적인 단점이 있는데 외부에서 변경이 불가능하다는 것이다.
    • 만약 테스트 코드를 작성해야 한다고 가정해보자. 테스트 코드는 일반적으로 스프링의 기능을 사용하지 않고 JUnit 등의 프레임워크를 사용하는 순수 자바 코드를 작성해야 한다.
    • 그런데 위와 같이 필드에 바로 의존관계 주입이 박혀있으면 스프링 프레임워크를 통해 의존관계 주입을 하지 않고서는 테스트가 불가능하다. 즉 스프링과 같은 DI 프레임워크에 의존적으로 변한다.
  • 위과 같은 치명적인 단점들로 인해 사용되는게 권장되지 않는다. 이는 스프링 공식 문서에서도 나와 있으며 사용하는 곳은 말 그대로 테스트 코드에서 의존관계 주입을 할 때나 @Configuration같은 곳에서 제한적으로 사용해야 한다.
  • 만약 순수 자바 테스트코드로 위의 필드들을 조작해야 하는 경우에는 결국 다음과 같이 Setter를 사용해주어야 한다.
@Component
public class OrderServiceImpl implements OrderService{
    @Autowired
    private MemberRepository memberRepository;
    
    @Autowired
    private DiscountPolicy discountPolicy;
    
    public void setMemberRepository(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }
    
    public void setDiscountPolicy(DiscountPolicy discountPolicy){
        this.discountPolicy = discountPolicy;
    }
    
    ...
}
  • 그런데 코드의 폼이 어딘가 유사하지 않는가? 바로 Setter Injection 코드와 매우 유사하게 변한다.
  • 따라서 이렇게 필드로 직접 주입하는것 보다는 그냥 Setter Injection으로 수행하는게 낫고 결과적으로 테스트를 위해서는 위와 같이 수렴된다.

 

 

 

정리

  • 의존관계 주입 방법에 대해 상세히 알아보았다.
  • 메서드 주입 방법의 경우 Constructor Injection과 그 코드 꼴이 매우 유사하며 결국 Injection Method를 호출하여 의존관계를 주입하기 때문에 Setter와 동일한 단점을 가지게 된다.
  • 해당 의존관계 주입 방법들 중에서 가장 좋은 방식은 Constructor Injection인데 그 이유와 다른 의존관계 주입 방식이 사용하기에 불편한 이유들을 다음 포스트에서 다루어 보도록 하겠다.
복사했습니다!