@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;
}
}
@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;
}
}
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
❗ 참고: 순수한 자바 테스트 코드에서는 @Autowired가 동작하지 않는다. @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(required - false)
: 자동 주입할 대상이 없으면 수정자 메소드 자체가 호출되지 않는다.org.springframework.lang.@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);
}
생성자 주입을 권장하는 이유는 아래와 같다.
✔ 불변
ac.getBean(DiscountPolicy.class)
와 같이 동작한다. (실제로는 더 많은 기능을 제공)NoUniqueBeanDefinitionException
오류가 발생한다.//기존 코드
@Autowired
private DiscountPolicy discountPolicy
//필드 명을 빈 이름으로 변경
@Autowired
private DiscountPolicy rateDiscountPolicy
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
//생성자 자동 주입 예시
@Autowired
public OrderServiceImpl(MemberRepository memberRepository
, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
NoSuchBeanDefinitionException
예외 발생@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
package hello.core.autowired;
import hello.core.AutoAppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Grade;
import hello.core.member.Member;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import
org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
public class AllBeanTest {
@Test
void findAllBean() {
//아래 코드는 스프링 컨테이너를 생성하면서, 파라미터에 정의된 클래스들을 자동으로 스프링 빈에 등록해준다.
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member member = new Member(1L, "userA", Grade.VIP);
int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
assertThat(discountService).isInstanceOf(DiscountService.class);
assertThat(discountPrice).isEqualTo(1000);
}
//DiscountService는 Map으로 모든 DiscountPolicy를 주입 받는다. (fixDiscountPolicy, rateDiscountPolicy)
static class DiscountService {
//map의 키에 스프링 빈의 이름을 넣어주고, 그 값으로 DiscountPolicy 타입으로 조회된 모든 스프링 빈을 담아준다.
//만약 해당하는 타입의 스프링 빈이 없으면, 빈 컬렉션이나 Map을 주입한다.
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;
System.out.println("policyMap = " + policyMap);
System.out.println("policies = " + policies);
}
//discount()는 discountCode로 주입받을 스프링 빈의 이름을 받아서 해당 빈을 찾아서 실행한다.
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
System.out.println("discountCode = " + discountCode);
System.out.println("discountPolicy = " + discountPolicy);
return discountPolicy.discount(member, price);
}
}
}
❓ 어떤 경우에 컴포넌트 스캔과 자동 주입을 사용해야 하고, 어떤 경우에 설정 정보를 통해 수동으로 빈을 등록하고, 의존관계도 수동으로 주입해야 할까?
참고 Refernce