스프링에서 의존성을 주입받는 방법
스프링에서 의존성을 주입받는 방법
1) 생성자를 통한 의존성 주입
생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다. 불변, 필수 의존관계에 사용된다.
생성자가 딱 하나만 있을 때는 @Autowired를 생략해도 자동 주입된다. (스프링 빈에만 해당된다.)
@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 어노테이션을 사용한 생성자 주입 방법
@RequiredArgsConstructor 어노테이션은 final이 붙거나 @NotNull이 붙은 필드의 생성자를 자동 생성해주는 lombok 어노테이션이다. 위 코드를 @RequiredArgsConstructor 어노테이션을 사용한 생성자 주입 방법으로 바꿀 수 있다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
2) 필드에서 주입받을 때
필드에 바로 주입하는 방법이다. 외부에서 변경이 불가능해서 테스트하기가 어려워 결국 해당 클래스에서 setter를 만들고, 테스트하는 쪽에서 setter를 호출해서 값을 넣어줘야한다. DI 프레임워크가 없으면 아무것도 할 수 없다. 권장하지 않는다. 다만, 테스트 코드 만들 때는 외부에서 가져다가 쓰지 않기 때문에 사용하고, 스프링 설정 목적으로 하는 @Configuration 같은 곳에서만 사용한다.
@Component
public class OrderServiceImpl implements OrderService {
@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;
}
3) setter 메서드를 통해 주입받을 때 (수정자 주입)
setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.
선택, 변경 가능성이 있는 의존관계에 사용한다. @Autowired는 기본 동작은 주입할 대상이 없으면 오류가 발생하므로 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false)로 지정하면 된다.
@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;
}
}
4) 일반 메서드 주입
일반 메서드를 통해 주입 받을 수 있다. 한 번에 여러 필드를 주입 받을 수 있지만 일반적으로 잘 사용하지 않는다.
의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다. 스프링 빈이 아닌 다른 클래스에서 @Autowired를 해도 아무 동작을 하지 않는다.
생성자 주입을 권장하는 이유
의존관계는 한 번 주입이 되면 불변해야 한다. 수정자 주입은 setter 메서드를 public으로 열어야 하기 때문에 변경이 일어날 수 있다. 변경하면 안되는 메서드를 public으로 열어두는 것은 좋은 방법이 아니다. 생성자 주입은 객체 생성 시 한 번만 호출되므로 불변하게 설계할 수 있다.
주입 데이터를 누락했을 때 컴파일 오류가 발생되고, 필드에 final 키워드를 사용할 수 있는데 생성자에서 값이 설정되지 않았을 때 컴파일 오류를 통해 캐치할 수 있다.
*수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없다. (오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.)
@Autowired 옵션 처리
public class AutoWiredTest {
@Test
void AutowiredOption() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
}
static class TestBean {
// 자동 주입할 외존관계가 없으면 메서드가 호출되지 않는다.
@Autowired(required = false)
public void setNoBean1(Member noBean1) {
System.out.println("noBean1 = " + noBean1);
}
// 자동 주입할 대상이 없으면 null을 반환한다.
@Autowired
public void setNoBean2(@Nullable Member noBean2) {
System.out.println("noBean2 = " + noBean2);
}
// 스프링 빈이 없으면 Optional.empty를 반환한다.
@Autowired
public void setNoBean3(Optional<Member> noBean3) {
System.out.println("noBean3 = " + noBean3);
}
}
}
빈 조회 시 2개 이상일 때 해결 방법
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy
다음과 같이 DiscountPolicy 타입에 대한 의존 관계를 주입할 때 DiscountPolicy의 하위 타입 2개를 모두 스프링 빈으로 선언하면 싱글톤이 깨져서 오류가 발생하게 된다. 다음과 같이 3가지 방법으로 해결할 수 있다.
- @Autowired 필드명 매칭
타입 매칭을 시도하고 타입 매칭 결과가 2개 이상인 경우 필드명 또는 파라미터명으로 빈 이름을 매칭한다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = rateDiscountPolicy;
}
- @Qualifier끼리 매칭
추가 구분자를 붙여서 주입 시 추가적인 방법을 제공하는 것으로 빈 이름을 변경하는 것은 아니다.
주입 시에 @Qualifier를 붙여주고 등록한 이름을 적어준다. @Qualifier끼리 매칭한다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {
if(member.getGrade() == Grade.VIP) return price * discountPercent / 100;
else return 0;
}
}
// 생성자 자동 주입
// 만약 @Qualifier("mainDiscountPolicy")를 찾지 못하면, mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다. 그래도 못 찾으면 NoSuchBeanDefinitionException 예외가 발생한다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Qualifier 방법의 단점은 다음과 같이 문자를 적으면 컴파일 시 타입 체크가 되지 않는다. 이것을 해결하기 위해 어노테이션을 만들어서 스프링 빈에 등록할 하위 타입 클래스에 선언한다.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {
}
- @Primary 사용
우선순위를 정하는 방법으로 @Autowired 시 여러 빈에 매칭되면 @Primary가 우선권을 갖는다.
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
스프링은 자동보다는 수동이, 넓은 범위의 선택권 보다는 좁은 범위의 선택권이 우선 순위가 높다. 따라서, @Primary 보다 @Qualifier가 우선권이 더 높다.
*참고 자료
스프링 핵심 원리 (김영한님)
Author And Source
이 문제에 관하여(스프링에서 의존성을 주입받는 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@gkskaks1004/스프링에서-의존성을-주입받는-방법저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)