스프링

스프링 핵심 원리 기본 - 의존관계 자동 주입

Rudtjs 2022. 7. 26. 08:58

다양한 의존관계 주입 방법

생성자 주입

이름 그대로 생성자를 통해서 의존 관계를 주입한다.

@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번만 호출한다.
  • 불변, 필수 의존관계에 사용
  • 생성자가 1개만 있을 경우 @Autowired는 생략이 가능한데 되도록 생략하지 말자.

 

생성자 주입을 가장 많이 쓰는 이유

  • (불변) 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다.
  • (누락) final 키워드로 생성자가 없을 시 컴파일 오류를 띄워준다.
  • 프레임워크에 의존하지 않고, 순수 자바 언어의 특징을 잘 살린다.

 

수정자 주입(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;
        }
}
  • 선택, 변경 가능성이 있는 의존관계에 사용
  • 자바 빈 프로퍼티 규약은 수정자 메서드 방식을 사용한다.
@Autowired는 주입할 대상이 없으면 오류가 발생하니 조심하자.

 

필드 주입

@Autowired 어노테이션을 사용하여 필드에 주입하는 방식이다.

@Component
public class OrderServiceImpl implements OrderService {
    
	@Autowired
	private MemberRepository memberRepository;
    
	@Autowired
	private DiscountPolicy discountPolicy;
}
  • 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 사용한다.
  • @SpringBootTest처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.
  • 외부에서 변경이 불가능하고 테스트하기 힘들다는 단점이 있다.

 

일반 메서드 주입

일반 메서드를 통해서 주입하는 방식이다.

@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;
    }
}
  • 한 번에 여러 필드를 주입받을 수 있다.

 

옵션 처리

주입할 스프링 빈이 없어도 동작해야 할 때가 있다. 하지만 @Autowired는 기본값이 true이기 때문에 자동 주입 대상이 없으면 오류가 발생한다. 

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

 

롬복을 이용한 생성자 주입

롬복(Lombok) 이란 getter, setter 등 반복 메서드를 어노테이션으로 처리할 수 있게 해주는 Java 라이브러리이다. 

 

맨 처음 만들었던 생성자 주입 코드를 롬복을 이용해서 줄여 보겠다.

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

이런 식으로 롬복이 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다.

 

조회 대상 빈이 2개 이상일 때 해결 방법

 

DIscountPolicy의 하위 타입인  FixDiscountPolicy , RateDiscountPolicy를 둘 다 스프링 빈으로 선언하고

@Component
public class FixDiscountPolicy implements DiscountPolicy {}

@Component
public class RateDiscountPolicy implements DiscountPolicy {}

이 상태에서 의존 관계를 주입해보자

// 의존 관계 자동 주입
@Autowired
private DiscountPolicy discountPolicy

NoUniqueBeanDefinitionException 오류가 뜨는걸 확인할 수가 있다. 수동 빈등록으로 해결할 수는 있지만, 앞서 말했듯 수동, 자동 빈등록을 같이 사용해서 버그가 터지면 잡기 힘들다는 문제점이 있다. 그래서 의존 관계 자동 주입을 통해 해결해보겠다.

 

 

@Autowired 필드 매칭

@Autowired 는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭 한다.  

// 기존 코드 변경
@Autowired
private DiscountPolicy rateDiscountPolicy

필드 명을 빈 이름으로 변경하면서  정상적으로 주입된다.

 

@Qualifier 사용

@Qualifier 는 추가 구분자를 붙여주는 방법이다. (빈 이름 변경 x)

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}

주입 시에 @Qualifier를 붙여주고 등록한 이름을 적어준다. 그리고 주입할 곳에도 적어주고 매칭 해주면 적용이 된다.

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

 

  1. 해당 @Qualifier가 붙은 빈을 조회한다.
  2. @Qualifier가 붙은 빈을 못찾으면 필드 또는 파라미터 이름으로 매칭을 시도한다.
  3. 그래도 찾지 못한다면 NoSuchBeanDefinitionException 예외 발생.

 

@Primary 사용

@Primary 는 우선순위를 정하는 방법이다. @Autowired 시에 여러 빈이 매칭 되면 @Primary 가 우선권을 가진다.

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}

 

Primary와 Qualifier 모두 등록 되어 있다면 이름을 직접 지정해주는 Qualifier가 우선순위를 갖는다.

 

 

출처

김영한 선생님 스프링 강의