둘셋 개발!

[spring] 의존관계 주입 본문

SPRING/spring 기본

[spring] 의존관계 주입

23 2022. 3. 13. 20:59

의존관계 주입은 4가지 방법이 있다.

1. 생성자 주입
2. 수성자 주입 (setter)
3. 필드 주입
4. 일반 메서드 주입

결론적으로 말하자면 생성자 주입을 쓰는 것이 바람직하다!


생성자 주입을 해야하는 이유

1. 의존관계는 불변해야 함

: 대부분 의존관계 주입은 한 번 일어나면 종료시 까지 변하지 않는다. 따라서 생성자는 딱 1번만 호출되기 때문에 생성자 주입이 알맞다

 

2. 의존관계 주입을 누락할 일이 없음

: 생성자 주입을 사용하면 주입 데이터가 누락 시 컴파일 오류가 발생한다.

 

3. 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;
		this.discountPolicy = discountPolicy;
	}
}

 

이 코드에서 더 줄일 수 있는 방법이 있다!!

바로 롬복 라이브러리에서 제공하는 @RequiredArgsConstructor 기능을 사용하면

final 키워드가 있는 필드를 모아서 자동으로 생성자를 만들어 준다.

 

다음은 최종 코드이다.

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

주입할 스프링 빈이 없어도 동작해야 할 때

@Autowired의 required옵션의 기본값이 true이기 때문에 자동 주입 대상이 없으면 오류가 난다.

하지만 자동 주입 대상이 없어도 동작해야 할 때 다음과 같이 하면된다.

- required의 값을 false: 자동 주입 대상이 없으면 수정자 메서드 자체가 호출이 안됨
- @Nullable: 자동 주입 대상이 없으면 null이 입력됨
- Optional<>: 자동 주입 대상이 없으면 Optional.empty가 입력됨

 

예시)

//메서드 자체가 호출이 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
        System.out.println("setNoBean1 = " + member);
    }
    
//null 호출
@Autowired
public void setNoBean2(@Nullable Member member) {
        System.out.println("setNoBean2 = " + member);
    }
    
//Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
        System.out.println("setNoBean3 = " + member);
    }

조회 빈이 2개 이상일 때

@Autowired는 타입으로 조회하기 때문에 같은 타입의 다른 빈이 2개 이상 존재할 때 문제가 발생한다.

 

예를 들어 할인정책을 세우는데 정액할인과 정률할인을 둘 다 스프링 빈으로 등록했을 때

//정액 할인 정책
@Component
  public class FixDiscountPolicy implements DiscountPolicy {}
  
//정률 할인 정책
@Component
  public class RateDiscountPolicy implements DiscountPolicy {}

의존 관계로 할인정책을 주입하고자 할 때

@Autowired
  private DiscountPolicy discountPolicy

타입으로 조회하기 때문에 두 개의 빈이 중복되기 때문에 NoUniqueBeanDefinitionException이 발생한다.

 

이 오류는 다음과 같이 해결 할 수 있다.

- @Autowired 필드 명 매칭
- @Qualifier 끼리 매칭
- @Primary 사용

NoUniqueBeanDefinitionException 해결방법

1. @Autowired 필드 명 매칭

@Autowire는 타입을 먼제 매칭하고 결과가 2개 이상 일때는 필드명, 파라미터 명으로 빈 이름을 배칭 하기 때문에

예를 들어 정률할인정책을 의존관계로 주입하고 싶으면

@Autowired
  private DiscountPolicy rateDiscountPolicy

이렇게 필드명을 rateDiscountPolicy로 바꾸면 된다.

 

2. @Qualifier 사용

@Qualifier는 추가 옵션으로 생각하면된다. 빈이름을 변경하는 것이 아니다.

정률할인정책을 의존관계로 주입하고 싶으면

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

이렇게 하면된다.

만약 @Qualifier("mainDiscountPolicy")를 찾지 못하게 되면 mainDiscountPolicy라는 빈이름을 찾는다

 

3. @Primary 사용

@Primary는 우선순위를 정하는 방법이다.

만약 정률할인정책을 우선권을 가지게 하려면

@Component
  @Primary
  public class RateDiscountPolicy implements DiscountPolicy {
 }

이렇게 하면 된다.

 

@Primary, @Qualifier활용

- 메인 데이터 베이스의 커넥션을 획득하는 스프링 빈은 @Primary를 적용하고 조죄하는 곳에서는 @Qualifier 지정 없이 편리하게 조회하고, 서브 데이터베이스 커넥션 빈을 획득할 때는 @Qualifier를 지정해서 명식적으로 획득 하는 방식으로 사용

(아직은 이게 무슨 말인지 모르겠지만,,,,, 나중에 알게 되겠지 하고 넘어갔다....ㅠㅠ)

-우선순위는 @Qualifier이 @Primary보다 높다

 


조회한 빈이 모두 필요할 때

예를 들어 정액 할인정책을 쓸 지 정률 할인정책을 쓸 지 클라이언트가 선택하게 할 때에는 빈을 모두 조회해야 한다.

static class DiscountService {
	private final Map<String, DiscountPolicy> policyMap;
	private final List<DiscountPolicy> policies;
    
	public DiscountService(Map<String, DiscountPolicy> policyMap,
    					List<DiscountPolicy> policies) {
    		this.policyMap = policyMap;
        	this.policies = policies;
	}
    
	public int discount(Member member, int price, String discountCode) {
		DiscountPolicy discountPolicy = policyMap.get(discountCode);
		return discountPolicy.discount(member, price);
	} 
}

➡️ 생성자 의존관계 주입 시 policyMap에는 key로 빈이름이, value에는 DiscountPoicy타입으로 조회한 빈을 넣는다

➡️ discount()를 할 때 파라미터 discountCode로 "rateDiscountPolicy"가 넘어오면 정률할인정책을 반영한 가격을 출력해준다

 


끝으로 실무 꿀팁!!

 

1. 자동으로 빈을 등록하자! 
2. 직접 등록하는 기술 지원 객체는 수동을 빈을 등록하자!
3. 다형성을 적극 활용하는 비지니스 로직은 수동 등록을 고민해보자!

 

 

(참고 : 인프런 김영한 강사님 - 스프링 핵심원리 - 기본편 )

'SPRING > spring 기본' 카테고리의 다른 글

[spring] 빈 생명주기 콜백  (0) 2022.03.14
[spring] 컴포넌트 스캔  (0) 2022.03.13
[spring] 싱글톤 컨테이너  (0) 2022.03.10
[spring] 스프링 컨테이너와 스프링 빈  (0) 2022.03.09