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

[섹션 7-3] 생성자 주입을 선택해라!!

보름달빵 2024. 2. 28. 22:23

 

 

의존관계를 주입하는 방식은 크게 4가지나 있는데 왜 생성자 주입을 쓰라고 하는 걸까? 

 

생성자 주입이 어떤 점에서 좋은지 자세히 알아보자. 

 

 

 

 

 

 

 생성자 주입 방식의 장점

 

 

 

 

 

 

 

 

먼저 OrderserviceImpl을 수정자 주입 방식으로 만들어보자. 

 

OrderServiceImpl

@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;
       }
       
     @Override
      public Order createOrder(Long memberId, String itemName, int itemPrice) {

        Member findMember = memberRepository.findById(memberId);
        
        // 할인과 관련된 부분은 disCountPolicy에 위임
        int discountPrice = discountPolicy.discount(findMember, itemPrice);

        return new Order(memberId,itemName,itemPrice,discountPrice);

    }
    
    }

 

 

OderServiceImplTest

class OrderServiceImplTest {

    @Test
    void createOrder(){

        OrderServiceImpl orderService = new OrderServiceImpl();
        orderService.createOrder(1L, "itemA", 10000);
    }

}

 

NullPointerException: 
Cannot invoke "hello.core.member.MemberRepository.findById(java.lang.Long)" 
because "this.memberRepository" is null

 

 

 

테스트를 실행시키니 memberRepository의 값이 없다는 오류가 발생했다. 

 

그 이유는 OrderServiceImpl의 createOrder는 memberRepository가 필요하다. 

테스트 코드에서 우리가 setMemberRepository 를 통해 memberRepository의 값을 설정해주지 않았기 때문에 memberRepository의 값이 없어서 NPE오류가 발생한 것이다. 

 

이 경우  setMemberRepository를 호출하여 memberRepository의 값을 준다면 오류가 해결될 것이다. 

 

 

( 수정자 주입을 이용해서 테스트 코드를 작성한다면, 객체를 생성할 때, 의존 관계가 있다면 이 값도 함께 설정해줘야해서 조금 번거롭다 ) 

 

 

 

 

But,   테스트 코드만 보고서는 OrderServiceImpl가 어떤 의존관계가 있는지 알 수 없다. 

그래서 우리는 해당 클래스의 의존 관계를 파악하기 위해서  직접 OrderServiceImpl의 코드를 읽어보고 필요한 객체의 값을 setter로 설정해줘야한다. 

 

물론,  지금처럼 오류 코드를 보고 OrderServiceImpl의 의존 관계를 파악하여 우리가 직접 설정해줘도 괜찮지만,

 

매번 이렇게 테스트 코드를 작성할 때 마다 해당 클래스가 어떤 의존관계를 가지고 있는지 코드를 뒤져보고 값을 설정해주기는 힘들다 

 

그래서 생성자 주입을 이용하는 것이 편리하다고 하는 것이다!! 

 

 

 

그렇다면, 생성자 주입을 이용하면 구체적으로 어떤 점이 좋은지 알아보자 

 


 

 

 

OrderServiceImpl의 의존관계 주입 방식을 생성자 주입 방식으로 바꿔서 테스트를 작성해보자.

 

OrderServiceImpl

@Component
public class OrderServiceImpl implements OrderService{

      private  final MemberRepository memberRepository;
      private  final DiscountPolicy   discountPolicy;

      @Autowired
      public OrderServiceImpl(MemberRepository memberRepository,@Qualifier("rateDiscountPolicy") DiscountPolicy discountPolicy) {

        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

 

 

OderServiceImplTest

class OrderServiceImplTest {

    @Test
    void createOrder(){

        MemberRepository memberRepository= new MemoryMemberRepository();
        memberRepository.save(new Member(1L,"오리", Grade.VIP));

        OrderServiceImpl orderService = new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy());
        orderService.createOrder(1L, "itemA", 10000);
    }

}

 

OrderServiceImpl orderService= new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy());

 

 

먼저 생성자 주입을 이용한다면, 생성자를 호출하는 과정에서 매개변수로 설정된 의존 관계에 있는 객체들의 값을   

반드시 넣어줘야한다. 

만약 값을 넣지 않는다면 바로 컴파일 오류가 발생해서 값을 넣어주라는 문구가 나온다. 

  • 그렇기 때문에 컴파일 시점에 오류를 잡아낼 수 있다. 
  • 그리고 해당 객체를 생성할 때 어떤 객체와 의존 관계에 있는지 테스트 코드만을 보더라도 파악이 가능하다. 
  • 또한, 매개변수에 값을 전달할 때 임의로 객체를 설정해서 넣어줄 수 있기 때문에 변경과 테스트에 편리하는 장점이 있다. 

 

 

그리고 생성자 주입을 이용하면 필드에 final 키워드를 사용할 수 있다. 

  • 그래서 생성자에서 혹시라도 값이 설정되지 않은 오류를 컴파일 시점에 막아준다. 
  • 또한, final 키워드를 사용하면 값을 직접 설정해주거나 생성자를 통해서만 값을 설정해 줄 수 있기 때문에 한번 객체를 주입 받으면 그 값이 변하지 않으므로 객체의 값이 불변이다. 

 

 

 

정리

왜 생성자 주입을 이용해야하는가?

 

 대부분의 애플리케이션이 종료될 때 까지 의존관계를 변경할 일이 없다

  • 생성자 주입을 이용할 경우, 처음 세팅해놓고 바꿀 수없으므로 좋음 -> "불변" 
  • 생성자 주입은 객체를 생성할 때 1번만 호출 되므로 불변하게 설계할 수 있다.
    • 수정자 주입의 경우에는 변경 가능성이 있다
    • 필드 주입은 스프링이 없다면 테스트 코드에서는 의존 객체의 값을 넣어줄 수가 없다. ( = DI 컨테이너에 의존적이다 ) 
  • 순수한 자바 코드로 테스트를 생성시, 생성자를 호출하기 위해 임의의 객체를 만들어줘야하므로 의존 관계를  파악하기 좋고, 내가 원하는 것으로 대체 할 수 있다는 장점이 있다
  • final 키워드를 사용할 수 있다는 것
    생성자에서만 값을 넣어줄 수 있기 때문에 한번 값을 주입해주고 나면 객체가 바뀌지 않는다
    또한 final 키워드를 사용하면 생성자를 만들때 값을 초기화 해주는 코드를 반드시 작성해야하므로 만약 코드가 누락 되었다면 컴파일 오류가 발생한다.

 

생성자 주입을 사용해야하는 이유 정리

 

 

 

▶  생성자 주입을 이용하자