오봉이와 함께하는 개발 블로그

Spring - 의존관계 자동 주입(옵션 처리, 생성자 주입을 선택하자) 본문

BE/Spring

Spring - 의존관계 자동 주입(옵션 처리, 생성자 주입을 선택하자)

오봉봉이 2022. 6. 10. 17:42
728x90

옵션 처리

스프링 빈을 옵셔널 하게 해서 등록을 안 해도 기본 로직으로 동작하게 하거나, 기본이 없으면 실행하지 않게 하는 등 주입할 스프링 빈이 없어도 동작해야 할 때가 있는데, @Autowired의 required라는 옵션은 기본값이 true로 되어 있어 자동 주입 대상이 없으면 오류가 발생한다.

  • 자동 주입 대상을 옵션으로 처리하는 방법
    • @Autowired(required = true) : 자동 주입할 대상이 없으면 수정자 메소드 자체가 호출 안됨
    • org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 Null이 입력
    • Optinal<> : 자동 주입할 대상이 없으면 Optional.empty가 입력된다.

테스트 코드

public class AutowiredTest {
    @Test
    void AutowiredOption() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);

    }

    static class TestBean {

        // 자동 주입할 대상 없으면 수정자 메소드 호출 안됨
        @Autowired(required = false)
        public void setNoBean1(Member noBean1) {
            System.out.println("NoBean1 = " + noBean1);
        }
        // 자동 주입할 대상이 없으면 null 입력
        @Autowired
        public void setNoBean2(@Nullable Member noBean2){
            System.out.println("noBean2 = " + noBean2);
        }
        // 자동 주입할 대상 없으면 Optional.empty 입력됨
        @Autowired
        public void setNoBean3(Optional<Member> noBean3) {
            System.out.println("noBean3 = " + noBean3);
        }
    }
}
// 출력 결과
noBean2 = null
noBean3 = Optional.empty
  • Member는 스프링 빈이 아니기 때문에 호출이 안됐다.
    • setNoBean1()은 @Autowired(required=false)이기 때문에 호출 자체가 안됨

참고

@Nullable, Optional은 스프링 전반에 걸쳐서 지원된다. 생성자 자동 주입할 때 @Nullable을 넣어주면 파라미터가 n개 있을 때 특정 파라미터는 스프링 빈에 존재하지 않아도 생성자 호출을 하고싶다면 할 수는 있다.

생성자 주입을 선택하자

과거에는 수정자 주입, 필드 주입을 많이 사용했지만, 최근 DI 프렝미워크 대부분이 생성자 주입을 권장함

이유 1. 불변

  • 대부분 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다.
    • 오히려 애플리케이션 종료 전까지 변하면 안됨.
  • 수정자 주입을 사용하면 setXXX메소드를 public으로 열어두어야 함.
    • 누군가 실수로 변경할 수도, 변경하면 안되는 메소드를 열어두는 것은 좋은 설계 방법이 아님.
  • 생성자 주입은 객체를 생성할 때 딱 한 번만 호출되므로 불변하게 설계할 수 있다.

이유 2. 누락

프레임워크 없이 순수한 자바 코드를 단위 테스트 할 때 수정자 의존관계인 경우 @Autowired가 프레임워크 안에서 동작할 때 의존관계가 없으면 오류가 발생하지만, 프레임워크 없이 순수한 자바 코드로 단위 테스트를 수행하면 오류가 발생한다.

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;
      }
//...
}
@Test
  void createOrder() {
      OrderServiceImpl orderService = new OrderServiceImpl();
      orderService.createOrder(1L, "itemA", 10000);
  }

해당 코드는 실행되지만 Null Pointer Exception이 발생된다.
-> memberRepository, discountPolicy 모두 의존관계 주입이 누락되었기 때문!

하지만 생성자 주입을 사용하면 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다. 또 IDE에서 어떤 값을 넣어줘야 하는지 알 수 있다.

// 생성자 주입 테스트 코드
public class OrderServiceImplTest {
    @Test
    void createOrder() {
        MemoryMemberRepository memberRepository = new MemoryMemberRepository();
        memberRepository.save(new Member(1L, "name", Grade.VIP));

        OrderServiceImpl orderService = new OrderServiceImpl(memberRepository, new FixDiscountPolicy());
        Order order = orderService.createOrder(1L, "itemA", 10000);
        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }
}

이유 3. final 키워드

  • 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다.
  • final은 초기값을 넣어주는 것이 아닌 이상 생성자에서만 값을 세팅할 수 있다.
  • 생성자에서 혹시 값이 설정되지 않는 오류를 컴파일 시점에 막아주는 장점이 있다.
@Component
  public class OrderServiceImpl implements OrderService {
      private final MemberRepository memberRepository;
      private final DiscountPolicy discountPolicy;
@Autowired
      public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
  discountPolicy) {
          this.memberRepository = memberRepository;
      }
//...
}
  • discountPolicy 에 값을 설정해야 하는데, 이 부분이 누락되었다.
    • 자바는 컴파일 시점에 오류를 발생시킨다.
      • java: variable discountPolicy might not have been initialized
      • 컴파일 오류는 세상에서 가장 빠르고 좋은 오류다.

참고

수정자 주입을 포함 나머지 주입 방식은 모두 생성자 이후에 호출되기 때문에 필드에 final 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.

정리

프레임워크에 의존하지 않으며, 순수한 자바 언어의 특징을 잘 살리는 방법인 생성자 주입은 생성자 주입 방식을 선택하는 이유 중 하나이다.
기본적으로 생성자 주입을 사용하고, 필수 값이 아닌 경우 수정자 주입 방식을 옵션으로 부여하자. 생성자 주입과 수정자 주입은 동시에 사용할 수 있다!
항상 생성자 주입을 선택하자! 옵션이 필요할 때만 수정자 주입을 선택하고, 필드 주입은 금물이다.

출처 : 인프런 김영한 지식공유자님의 스프링 완전 정복 로드맵 강의
728x90
Comments