의존관계 주입 방법
- 생성자 주입 (가장 굿)
- 수정자 주입(setter 주입)
- 필드 주입
- 일반 메서드 주입
생성자 주입
- 생성자를 통해 의존관계를 주입받는 방법
- 지금까지 진행한 방법
특징
- 생성자 호출시점에 딱 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를 생략해도 자동 주입된다.(물론 스프링 빈에만 해당)
수정자 주입(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;
}
}
필드주입
- 필드에 바로 주입하는 방식
특징
- 코드가 간결해서 편리해보이지만 외부에서 변경이 불가능해 테스트하기 힘듦
- DI프레임워크가 없으면 아무것도 못함
- 사용하지 말기
- 실제 코드와 관계없는 테스트 코드정도에만 사용
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
일반 메서드 주입
- 일반 메서드를 통해 주입받을수 있다.
특징
- 한번에 여러필드를 주입받을수 있다.
- 잘 사용안함
@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 가 입력된다.
생성자 주입을 선택해라!
final 키워드
- 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
불변
누락
조회 빈이 2개 이상 - 문제
- @Autowired 는 타입(Type)으로 조회한다.
@Autowired
private DiscountPolicy discountPolicy
- 다음과 같이 같은 타입(부모타입)을 갖고 있는 스프링 빈이 두개라면 NoUniqueBeanDefinitionException 오류가 발생
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
- 하위 타입으로 지정할 수 도 있지만, 하위 타입으로 지정하는 것은 DIP를 위배하고 유연성이 떨어진다. 그리고 이름만 다르고, 완전히 똑같은 타입의 스프링 빈이 2개 있을 때 해결이 안된다.
해결방안
@Autowired 필드 명 매칭
- @Autowired는 타입 매칭을 시도하고, 여러빈이 있으면 필드이름, 파라미터 이름으로 빈이름을 추가한다.
- 스프링 컨테이너에서 DiscountPolicy 타입을 탐색하고 2개이상일시 rateDiscountPolicy를 이름으로 탐색
@Autowired
private DiscountPolicy rateDiscountPolicy
@Qualifier 사용
- @Qualifier는 추가 구분자를 붙여주는 방법
- 빈 등록시 @Qualifier를 붙여 준다(자동등록 , 수동등록 동일)
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Bean
@Qualifier("mainDiscountPolicy")
public DiscountPolicy discountPolicy() {
return new ...
}
- 주입시에 @Qualifier를 붙여주고 등록한 이름을 적어준다.
- 만약 @Qualifier로 주입할때 @Qualifier("mainDiscountPolicy")를 못찾으면 mainDiscountPolicy라는 이름의 스프링빈을 추가로 찾는다. 하지만 @Qualifier는 @Qualifier를 찾을때만 사용하는것이 좋다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Primary 사용
- 우선순위를 정하는 방법이다. 같은 타입의 스프링빈이 있다면 등록시에 @Prime이 있는 스프링빈이 우선 순위를 갖는다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
우선권
- @Qualifier > @Prime -> 항상 좀더 세세한 것에 우선권을 준다.
- 조회한 빈이 모두 필요할 때, List, Map
- 의도적으로 해당 타입의 스프링 빈이 모두 필요할수 있다.
- ex ) 할인서비스를 제공하는데, 클라이언트가 할인의 종류를 선택할수 있다고 가정한다면 같은 타입 DiscountPolicy로 하는 모든 빈(rate, fix)이 필요하다.
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);
}
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;
System.out.println("policyMap = " + policyMap);
System.out.println("policies = " + policies);
}
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);
}
}
}
자동, 수동의 올바른 실무 운영 기준
편리한 자동 기능을 기본으로 사용하자
- 설정 정보를 기반으로 애플리케이션을 구성하는 부분과 실제 동작하는 부분을 명확하게 나누는 것이 이상적이지만, 개발자 입장에서 스프링 빈을 하나 등록할 때 @Component 만 넣어주면 끝나는 일을 @Configuration 설정 정보에 가서 @Bean 을 적고, 객체를 생성하고, 주입할 대상을 일일이 적어주는 과정은 상당히 번거롭다.
- 또 관리할 빈이 많아서 설정 정보가 커지면 설정 정보를 관리하는 것 자체가 부담이 된다. 그리고 결정적으로 자동 빈 등록을 사용해도 OCP, DIP를 지킬 수 있다.
수동 빈 등록은 언제 사용하면 좋을까?
- 기술 지원 빈: 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용된다. 데이터베이스 연결이나, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들이다.
- 애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서 딱! 설정 정보에 바로 나타나게 하는 것이 유지보수 하기 좋다.
- 비즈니스 로직 중에서 다형성을 적극 활용할 때(DiscountPolicy 안에 rate와fix같이 여러개의 역할이 있는경우)
참고강의
728x90
'Spring' 카테고리의 다른 글
[spring] 게시판 CRUD 만들기 (0) | 2022.12.30 |
---|---|
JPA Auditing기능이란? (0) | 2022.12.29 |
스프링 핵심 원리[기본편] - 컴포넌트 스캔 (0) | 2022.12.27 |
스프링 핵심 원리[기본편] - 싱글톤 컨테이너 (0) | 2022.12.27 |
스프링 핵심 원리[기본편] - 스프링 컨테이너와 스프링 빈 (0) | 2022.12.27 |