본문 바로가기

Spring Study

[스프링 핵심 원리] - 기본편 #7

섹션 8 : 의존관계 자동 주입

의존관계 주입의 4가지 방법

  • 생성자 주입
  • 수정자 주입(setter 주입)
  • 필드 주입
  • 일반 메서드 주입

 

생성자 주입

@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;
    }

위의 코드처럼 생성자를 통해 의존관계를 주입 받는 경우이다. 

  • 생성자 호출시점에 딱 1번만 호출되어, 값을 세팅한 이후에는 변경할 수 없다. 
  • 불변, 필수 의존관계에 사용한다.

 

private final MemberRepository memberRepository;

private final로 선언된 경우, 반드시 값이 존재해야 한다. 

discountPolicy처럼 값이 없을 경우에는 컴파일 오류가 발생한다.

위의 사진처럼 생성자가 딱 1개만 있는 경우, @Autowired를 생략해도 자동 주입 된다

 

 

수정자 주입(Setter 주입)

@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        System.out.println("memberRepository = " + memberRepository);
        this.memberRepository = memberRepository;
    }

    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        System.out.println("discountPolicy = " + discountPolicy);
        this.discountPolicy = discountPolicy;
    }

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        System.out.println("1. OrderServiceImpl.OrderServiceImpl");
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해 의존관계를 주입하는 방법이다.

 

  • 스프링 빈이 등록된 이후에 연관 관계를 자동으로 주입한다.
  • 스프링의 객체 생성 단계와 의존 관계 설정 단계가 분리되어 있다.
  • 생성자 주입은 빈 등록시 의존 관계 주입이 이루어지고, 수정자는 두 번째 단계에서 주입된다.
  • 선택, 변경 가능성 있는 의존관계에 적합하다.
@Autowired(required = false)
public void setMemberRepository(MemberRepository memberRepository) {
    System.out.println("memberRepository = " + memberRepository);
    this.memberRepository = memberRepository;

 @Autowired의 기본 동작은 주입할 대상이 없으면 오류가 발생한다.

따라서 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false)로 지정하면 된다. (선택적)

 

 

필드 주입

@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;

 의존관계를 필드에 바로 넣어주는 것 

코드도 간결해서 좋지만, 외부에서 변경이 불가능해서 테스트 하기가 매우 어렵다는 단점이 있다.

실제로 인텔리제이에서 권장하지 않는다는 경고가 뜬다.

스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용해야 한다 ! 

 

 

일반 메서드 주입

@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

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

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

일반 메서드를 통해 주입받을 수 잇어서 한번에 여러 필드를 주입 받을 수 있다.

생성자 주입과 수정자 주입을 대부분 사용하기 때문에 일반적으로 잘 사용하지 않는다.

 

의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다.

 

자동 주입 대상을 옵션으로 처리하는 방법

  • @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출이 안된다
  • @Nullable : 자동주입할 대상이 없으면 null이 입력된다
  • Optional<> : 자동 주입할 대상이 없으면 Optional.empty가 입력된다.

생성자 주입 - 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자주입을 권장한다. 

 

이유 1. 불변

의존관계 주입은 한 번 일어나면 대부분 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 

(대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.)

 

이유 2. 누락

생성자 주입을 사용하면 데이터를 누락했을 때 컴파일 시점에서 오류가 발생한다.

 

이때 필드에 final 키워드를 사용하면 값이 설정되는 오류를 컴파일 시점에 막아준다. 

위 코드에서는 private final MemberRepository memberRepository;와 private final DiscountPolicy discountPolicy;

가 생성자를 통해 초기화되어야 한다.

하지만 현재 생성자 내부에서 해당 필드에 값을 할당하지 않아서 컴파일 오류(빨간 줄)가 발생한다.

 

final (상수)

  • final 키워드를 사용하면 한 번 값이 할당된 후 변경할 수 없기 때문에, 객체의 상태를 불변으로 유지할 수 있다.
  • 이는 예상치 못한 버그를 방지하는 데 도움이 된다.

 

lombok

@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;
     }
}

 기존의 코드

이전 시점보다 정말 많이 간결해지긴 했지만, lombok 라이브러리를 사용하면 다르다. 

 

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
}

 

lombok 라이브러리에서 사용하는 @RequiredArgsConstroctor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다.