Spring

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

코드파고 2022. 4. 27. 00:28

[스프링 의존관계 주입 방법]

1. 생성자를 통한 의존관계 주입 -> 주로 불변, 필수 의존관계에 사용된다.

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    System.out.println("memberRepository = " + memberRepository);
    System.out.println("discountPolicy = " + discountPolicy);
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

- 생성자 호출시점에 딱 한번만 호출되는것이 보장된다. 

** 생성자가 딱 한 개만 있다면 Autowired 생략가능

 

2. 수정자 주입 -> 선택, 변경이 가능한 의존관계에 사용

 

required=false 조건 : 주입할 대상이 없어도 동작하게 할 경우

@Autowired(required = false)
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
    System.out.println("discountPolicy = " + discountPolicy);
    this.discountPolicy = discountPolicy;
}

 

3. 필드 주입

값의 변경이 용이하지 않다. 그래서 테스트에도 어려움이 생긴다. --> 권장하지 않는다.

* SpringBootTest : 자동으로 스프링 컨테이너를 띄워서 테스트한다.

SpringBootTest 내부에서 @Autowired 필드주입해서 사용하기도 한다.

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

 

 

4. 일반 메서드 주입 -> 한번에 여러 필드를 주입받는다 (수정자 주입과 유사)

일반적으로 생성자, 수정자 주입에서 거의 다뤄지기 때문에 잘 사용하지 않는다. 

@Override
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
}

 

의존관계 주입 👉 생성자 주입을 선택하게 된다 (의존관계는 애플리케이션 종료 시점까지 변하지 않는 것이 좋다.)

 

생성자 주입의 장점 :

- 값을 final로 설정할 수 있다.

private final MemberRepository memberRepository; // final로 설정해 생성자에서의 코드 누락을 막는다.
private final DiscountPolicy discountPolicy;

- framework에 의존하지 않고 순수 자바의 성향을 살릴 수 있다.

- 생성자 주입 + 수정자 주입을 추가적으로 구현하면 좋다.(필드 주입은 비권장)

 


[자동 주입 옵션 처리]

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

단순히 @Autowired만 사용하면 required가 true로 설정되어 오류가 발생하는 문제가 생긴다!

 

1) @Autowired(required = false) : 자동 주입  대상이 없으면 메서드 자체가 호출되지 않는다

@Autowired(required = false) // 스프링 컨테이너에 관리되지 않는 멤버 사용
public void setNoBean1(Member member) {
    System.out.println("member = " + member);
}

2) org.springframwork.lang.@Nullable : 자동 주입 대상이 없으면 null이 호출된다

@Autowired
public void setNoBean2(@Nullable Member noBean2) {
    System.out.println("noBean2 = " + noBean2);
}

3) Optional<> : 자동 주입 대상이 없다면 Optional.empty를 반환한다. (java8 문법)

@Autowired
public void setNoBean3(Optional<Member> noBean3){
    System.out.println("noBean3 = " + noBean3);
}

[Lombok]

getter, setter, constructor, toString 등 유용한 메서드들을 자동생성해주는 플러그인

 

@RequiredArgsConstructor

아래와 같은 생성자를 자동으로 생성해 준다! 매우 편하다 :D

의존관계가 추가될 때도 편리 :O final 필드와 함께 사용하면 금상첨화😍

private final MemberRepository memberRepository; // final로 설정해 생성자에서의 코드 누락을 막는다.
private final DiscountPolicy discountPolicy;

//RequriedArgsConstructor가 만드는 생성자
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

[조회하는 빈이 2개 이상일 경우]

NoUniqueBeanDefinitionException이 터진다.

그렇다고 하위타입으로 지정시 DIP가 깨지고 유연성이 떨어진다.

 

<✨해결방법>

1) @Autowired 필드명 매칭

타입이 같은 빈이 여러 개라면 👉 필드명 / 파라미터 명을 bean 이름으로 매칭해 등록한다

 

2) @Qualifier

추가 구분자를 붙여주는 방식. 빈 이름이 변경되지 않는다

Qualifier 끼리 매칭 후 👉 Bean 이름 매칭

@Component
@Qualifier("mainDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {
@Component
@Qualifier("fixDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {
public OrderServiceImpl(MemberRepository memberRepository,
		@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

3) @Primary

기본값처럼 동작해 등록한다. 👉 사용 빈도가 높은 빈을 우선적으로 생성해 주고 싶을 때 자주 사용한다.

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {

💬 @Qualifier & @Primary 함께 사용하기

사용 빈도가 높은 빈을 @Primary로 설정하고,

사용 빈도가 낮은 빈을 @Qualifier로 지정해 구현한다.

- 이 때, 우선순위는 세부적인 설정이 가능한 @Qualifier가 @Primary보다 높다.

 


[Annotation 만들어보기]

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
			ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
//Target~ Documented는 org.springframework.beans.factory.annotation.Qualifier에서 가져온 어노테이션
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
    
}

[조회한 빈이 모두 필요할 때] - List, Map 사용

특정 타입의 빈이 모두 필요한 경우[ex) fixDiscount, rateDiscount를 고객이 선택할 경우]에 사용된다

@Test
void findAllBean(){
    ApplicationContext ac = new AnnotationConfigApplicationContext
    		(AutoAppConfig.class, DiscountService.class);
            // 두 개 모두 설정해야 빈을 불러올 수 있다
    DiscountService discountService = ac.getBean(DiscountService.class);
@RequiredArgsConstructor
static class DiscountService {
    private final Map<String, DiscountPolicy> policyMap;
    private final List<DiscountPolicy> policies;

언제 자동, 수동으로 의존관계 주입을 할까?

자동으로 의존관계 주입을 해도 OCP, DIP는 깨지지 않는다.

자동 / 수동은 상황에 맞게 쓰는 것이 중요하다

 

수동 빈 등록은 언제 사용해야 할까?

업무 로직 빈 : 빈 컨트롤러, 서비스, 리포지토리를 가르킴. 비즈니스 요구사항에 따라 수정됨

기술 지원 빈 : 기술적인 문제나 공통 관심사를 처리할 때 사용 (데이터베이스 연결, 공통 로그 처리) 어플리케이션 전반에 영향을 미친다.

 

업무 로직 빈은 주로 자동 기능을 사용하고

기술지원 빈수동 빈 등록을 사용해서 눈에 보이게 설정 정보에 나타나게 하는 것이 좋다.

다만 다형성이 많이 드러나는 업무 로직 빈의 경우에도 수동 빈 등록을 사용한다.