[스프링/Spring] 의존관계 주입(DI)은 어떻게 할 수 있을까? - 여러가지 의존관계 주입 방법

kindof

·

2021. 8. 25. 14:51

🧑‍💻 들어가면서

의존관계 주입은 객체 지향 설계를 하는 데 있어서 정말 중요한 개념이죠. 의존 관계 주입 없는 쌩 자바 코드만으로는 DIP, OCP 등의 객체지향 설계 원칙을 지키기 힘들고, 객체끼리 서로 의존할 수밖에 없는 상황이 발생하기 때문입니다.

 

의존 관계 주입이 왜 필요하고 무엇인지에 대한 내용은 이전에 정리한 글도 있으니 참고하실 분들은 참고하시면 좋을 것 같습니다.

 

 

[스프링/Spring] 객체 지향 설계와 DI(Dependency Injection)의 시작(feat. 생성자 주입)

👻 0. 들어가면서 이전 포스팅에서 스프링이 만들어지고 각광받는 이유가 "객체 지향 프로그래밍의 장점을 극대화할 수 있기 때문"이라고 했다. 그리고 객체 지향 프로그래밍이란 '컴포넌트를

studyandwrite.tistory.com

 

이번 시간에는 의존 관계를 실제로 어떻게 주입하는지, 그 방법들에 대해 살펴보려고 합니다.

 

 

⭐️ 1. 생성자 주입

생성자 주입은 말그대로 생성자를 통해 의존관계를 주입하는 방법입니다. 가장 잘 나가는 방법인데 아래 내용을 통해 이해해보겠습니다.

@Component
public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

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

위 코드를 보면 OrderServiceImpl 클래스는 @Component 어노테이션을 달고 있으며, 생성자에 @Autowired라는 어노테이션을 달고 있습니다.

 

그러면 최초에 스프링에서 ComponentScan을 할 때 "OrderServiceImpl이라는 컴포넌트가 있구나~?"라고 스캔을 하고 스프링 빈에 등록이 될 때 생성자가 호출이 되겠죠.

 

생성자가 호출될 때는 "Autowired가 있네? 스프링 컨테이너에서 MemberRepository랑 DiscountPolicy를 꺼내서 의존관계 주입을 해줘야겠다"라는 흐름으로 이어집니다. 따라서 NullPointerException이 날 가능성이 현저히 줄어들죠. 참고로 위 예시처럼 생성자가 1개만 존재하면 @Autowired를 생략할 수도 있습니다. 아니면 @RequiredArgsConstructor를 써도 되겠죠?

 

한편, 의존관계를 주입하기 위해서는 MemberRepository, DiscountPolicy 역시 스프링 빈으로 등록이 되어있어야 합니다. 그렇지 않으면 아래와 같은 UnsatisfiedDependencyException에러가 빈 로딩 시점에 발생합니다. 

 

아래에서 설명할 수정자 주입(Setter 주입) 같은 경우에는 테스트 코드를 짤 때에도 의존관계 주입이 정상적으로 되지 않은 상황을 런타임에서야 잡아내지 못하는 문제가 있습니다.

 

한편, 생성자 주입은 생성자 호출 시에 딱 1번만 호출됩니다. 그렇기 때문에 불변적이며 필수적인 의존관계에 사용되죠. 위 코드에서 필드를 final로 선언해버릴 수 있는 이유도 여기에 있습니다. 

 

사실 대부분의 의존관계 주입은 한 번 일어나면 애플리케이션 종료시까지 의존관계를 변경할 일이 별로 없습니다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안되겠죠. 

 

생성자 주입은 가장 보편적으로 많이 사용되고, 하이라이트로 표시한 것처럼 얻을 수 있는 이득이 있기 때문에 좋은 의존관계 주입 방법으로 사용됩니다.

 

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

수정자 주입은 setter를 통해 의존관계를 주입하는 방법인데, 선택 가능하거나 변경 가능성이 있는(많지는 않다) 의존관계에 사용됩니다.

 

우리가 평소에 많이 쓰는 getter, setter 메서드에서 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 주입은 기본적으로 변경이 가능한 의존관계에 사용되기 때문에 위 코드에서 memberRepository나 discountPolicy는 final로 선언하면 안됩니다.

 

또한, 수정자 주입은 위에서 볼 수 있는 것처럼 setXXX 메서드를 열어두기 때문에 잘못된 의존관계의 수정이 가능해져버리는 문제가 생길 수 있습니다.

 

 

💉 3. 필드 주입

필드 주입은 말 그대로 필드에 의존관계를 바로 주입하는 방법입니다. 아래 예시처럼 코드가 간결해서 사용하기 좋을 것 같지만 외부에서 변경이 불가능하기 때문에 테스트하기 힘들다는 단점이 있습니다. 따라서 애플리케이션의 실제 코드와 관계 없는 테스트 코드를 작성할 때나 @Configuration같은 곳에서만 특별한 용도로 사용합니다.

 

무슨 말인지 아래 코드를 보겠습니다.

@Component
public class OrderServiceImpl implements OrderService{
    @Autowired private MemberRepository memberRepository;
    @Autowired private DiscountPolicy discountPolicy;

    ...
    ..
}

필드 주입은 위 코드처럼 private한 필드에도 바로 @Autowired를 때려 넣어서 편리해보입니다. 하지만 예를 들어 필드 주입을 한 다음에 개발자가 memberRepository나 discountPolicy를 다른 객체로 바꿔서 테스트를 해보고 싶으면 어떻게 할 수 있을까요?

 

의존 관계를 바꿀 수 있는 setter가 있는 것도 아니기 때문에 쉽지 않습니다. 

 

따라서 setter 메서드를 아래에 또 만들어줘야하죠. 그럴 바에 setter에 @Autowired를 쓰는게 낫지 않을까요?

 

💉 4. 일반 메서드 주입

일반 메서드 주입 역시 잘 사용하지는 않는데, 아래와 같이 사용합니다.

@Component
public class OrderServiceImpl implements OrderService{
    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;


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

수정자 주입이랑 거의 똑같다고 볼 수 있는데, 한 번에 여러 필드를 주입 받을 수 있다는 장점이 있기는 합니다.

 

 

🚗 나가면서

지금까지 의존관계 주입에 대한 네 가지 방법을 살펴보았습니다. 사실 대부분의 의존 관계 주입은 생성자 주입을 통해 이루어집니다. 생성자 주입이 가진 장점들 때문이죠.

 

한편, 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈들끼리의 관계이기 때문에 스프링 빈이 아닌 클래스에서 의존 관계를 주입하려고 해도 먹히지 않습니다. 참고하시면 좋을 것 같습니다.