스프링 핵심 원리 정리 - 7

의존 관계 자동 주입(2)

Lombok

개발을 하면 대부분 아래의 코드처럼 필드는 final 키워드를 사용하게 되고 생성자도 만들고 주입 받는 값을 대입하는 코드도 만들어야 한다.

@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

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

롬복이라는 라이브러리를 사용하면 이를 더 간단하게 할 수 있다. 먼저 build.gradle에 롬복에 관한 설정을 추가해준다.

// build.gradle

// lombok 설정
configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}
// lombok 설정 끝

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'

	// lombok 라이브러리 추가
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testCompileOnly 'org.projectlombok:lombok'
	testAnnotationProcessor 'org.projectlombok:lombok'
	// lombok 라이브러리 추가 끝

	...
}

롬복을 이용하여 위의 코드를 수정하면 아래와 같이 된다. @RequiredArgsConstructor 는 final이 붙은 필드로 생성자 제공한다. 가능한 이유는 롬복이 컴파일 시점에 자바의 annotation processor를 이용하여 생성자 코드를 자동으로 생성하기 때문이다.

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

}

@Autowired로 조회되는 빈이 2개 이상일 경우

@Autowired는 타입으로 조회하기 때문에 2개 이상의 하위 타입이 스프링 빈으로 등록될 경우 NoUniqueBeanDefinitionException 오류가 발생하게 된다. 이를 방지하기 위해 하위 타입을 지정하면 DIP를 위배하여 유연성이 떨어지게 된다. 의존 관계 자동 주입에서 이를 해결하는 방법은 3가지 정도가 있다.

@Autowired 필드 명 매칭

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

// 기존 코드

@Autowired
private DiscountPolicy discountPolicy;

// 필드 이름을 빈 이름으로 변경

@Autowired
private DiscountPolicy rateDiscountPolicy

@Qualifier

@Qualifier는 추가 구분자를 붙여주는 방식으로 주입 시 추가적인 방법을 제공하며 빈 이름을 변경하는 것은 아니다. 아래 예제에서는 생성자에서 사용했지만 setter와 직접 빈을 등록할 때도 사용할 수 있으며 @Qualifier로 추가한 빈을 찾지 못할 때는 같은 이름의 스프링 빈을 추가로 찾게 된다.

// RateDiscountPolicy

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

// FixDiscountPolicy

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

// OrderServiceImpl
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

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

@Primary

@Primary는 우선순위를 정하는 방법으로 @Autowired에서 여러 빈이 조회될 경우 @Primary가 우선권을 갖는다.

// RateDiscountPolicy

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

// FixDiscountPolicy

@Component
public class FixDiscountPolicy implements DiscountPolicy{
	...
}

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

@Primary vs @Qualifier

@Primary는 기본값처럼 동작하고 @Qualifier는 상세하게 동작한다. 스프링에서는 자동보다는 수동이, 넓은 범위의 선택권보다는 좁은 범위의 선택권이 우선 순위가 높으므로 @Qualifier가 우선권이 높다.