[Spring]스프링 핵심 원리(기본편) - 7. 의존관계 자동 주입

Wooyong Jung·2023년 9월 18일
0
post-thumbnail
post-custom-banner
  • 해당 게시물은 인프런 "스프링 핵심 원리 - 기본편" 강의를 참고하여 작성한 글입니다.
  • 자세한 코드 및 내용은 강의를 참고해 주시길 바랍니다.
    강의링크 -> 스프링 핵심 원리 - 기본편 (김영한)

Section7. 의존관계 자동 주입

  • 다양한 의존관계 주입 방법에 대해 알아봅니다
  • Lombok에 대해 알아보고 사용해봅니다
  • 조회 빈이 중복됐을 때의 문제를 인식하고 해결방법을 알아봅니다
  • 조회한 빈이 모두 필요할 경우 모든 빈을 가져오는 방법을 알아봅니다.

📄 다양한 의존관계 주입 방법

의존 관계 주입은 생성자 주입, 수정자 주입(setter 주입), 필드 주입, 일반 메서드 주입 으로 크게 4가지 방법이 있다.

1) 생성자 주입

  • 생성자를 통해서 의존 관계를 주입 받는 방법
  • 생성자 호출시점에 딱 1번만 호출되는 것이 보장
  • 불변, 필수 의존관계에 사용
@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개만 있을 경우 @Autowired를 생략해도 자동 주입이 된다.

2) 수정자 주입(setter 주입)

  • 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 애노테이션을 가진 매서드를 자동으로 실행하기 때문에 setter메서드를 따로 실행하지 않아도 자동으로 주입된다.

3) 필드 주입

  • 필드에 바로 주입하는 방법
  • 코드가 간결하지만 외부에서 변경이 불가능해서 테스트하기 힘들다
  • 되도록사용하지 않는 것을 추천
  • 애플리케이션의 실제 코드와 관계 없는 테스트 코드에는 사용 가능
@Component
public class OrderServiceImpl implements OrderService {

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

4) 일반 메서드 주입

  • 일반 메서드를 통해서 주입 받는 방법
  • 한번에 여러 필드를 주입 받을 수 있지만 잘 사용하지는 않는다
@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;
    }
}

과거에는 수정자 주입과 필 드 주입을 많이 사용했지만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다.

  • 불변: 대부분의 의존관계 주입은 한번 일어나면 종료시점까지 의존관계를 변경할 일 이 없다
  • 누락: 순수한 자바 코드로 테스트를 진행하는 경우 주입 데이터를 누락했을 때 컴파일 오류를 발생시킨다
  • final 키워드: 생성자에서 값이 설정되지 않는 오류(final 키워드는 값이 항상 있어야함을 의미)를 컴파일 시점에 막아준다
    -> 컴파일 오류는 세상에서 가장 빠르고, 좋은 오류다

📄 옵션 처리

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

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


📄 롬복과 최신 트랜드

Lombok 이란 Java 라이브러리로 반복되는 getter, setter, toString .. 등의 반복 메서드 작성 코드를 줄여주는 코드 다이어트 라이브러리이다

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

기존의 OrderServiceImple코드를 Lombok을 이용해서 최적화해보자

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

@RequiredArgsConstructor : final이나 @NotNull 이 붙은 필드의 생성자를 자동 생성해주는 애노테이션

최근에는 생성자를 1개 두고, @Autowired를 생략하는 방법을 주로 사용한다. 여기에 @RequiredArgsConstructor를 함께 사용하면 깔끔하게 코드를 작성할 수 있다. 가끔 생성자가 필요할 때도 있는데 그 경우에만 생성자를 직접 만들도록 하자.


📄 조회 빈이 2개 이상 - 문제

@Autowired는 빈을 타입으로 조회하는데 선택된 빈이 2개 이상일 때 문제가 발생한다. DiscountPolicy의 하위 타입인 FixDiscountPolicy, RateDiscountPolicy 둘 다 스프링 빈으로 선언하는 경우 NoUniqueBeanDefinitionException 오류가 발생하는데 이를 해결 할 수 있는 3가지 방법이 있다.

  • @Autowired 필드 명 매칭
  • @Qualifier @Qualifier끼리 매칭 빈 이름 매칭
  • @Primary 사용

📄 조회한 빈이 모두 필요할 때, List, Map

의도적으로 해당 타입의 스프링 빈이 다 필요한 경우도 있다. 예를 들어 클라이언트가 할인 서비스로 할인의 종류(rate, fix)를 선택할 수 있다고 가정하자.

static class DiscountService {

    private final Map<String, DiscountPolicy> policyMap;
    private final List<DiscountPolicy> policies;

    // @Autowired 생략가능
    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);
        System.out.println("discountCode = " + discountCode);
    }
}

DiscountServiceAutoAppConfig와 같이 스프링 빈으로 등록하면 DiscountPolicy타입인 DiscountPolicy, RateDiscountPolicy 모두 Map형태로 policyMap에 들어가게 되고 이를 이용하면 상황에 맞는 할인 서비스를 제공할 수 있게 된다.

profile
실패를 두려워하지 않는 백엔드 개발자가 되기 위해 노력하고 있습니다.
post-custom-banner

0개의 댓글