본문 바로가기
JAVA & SPRING/Spring 핵심원리-기본

스프링 핵심 원리 - 6일차 (의존관계 자동 주입)

by 눈오는1월 2023. 7. 30.
728x90

의존관계 주입에는 크게 4가지가 존재한다

1. 생성자 주입 -> 지금까지 햇던걸로 생성자를 호출할때 주입하는 것이며, 1번만 호출한다는 것이 보장되고 세팅 한번 이후에는 이후 세팅을 못하게 할 수 있다. (좋은 아키텍처는 적절한 제약이 있어야한다.) 또한 생성자 주입시 private final로 사용해야한다.

생성자가 1개만 있으면 @Autowired 생략해도 자동 주입 됨

 

2. 수정자 주입 -> setter라 불리는 필드의 값을 변경하는 메서드를 통해 의존관계를 주입하는 방식 스프링 컨테이너에 2가지 라이프 사이클이 있는데 빈을 등록하는 것과 의존관계를 등록하는 라이프 사이클이 있다. 생성자 주입은 빈을 등록하면서 동시에 의존관계 주입도 같이 일어나는데 setter는 빈을 등록하고 난 후 의존관계 주입 단계에서 일어난다.

 

3.필드주입 -> 필듣에 바로 주입을 하는 방식이다 되게 독버섯같은 존재인데 코드가 매우 간결해져서 좋아보이지만 값을 외부에서 변경이 불가능 해서 순수 자바 테스트가 힘들다. -> 이걸 해결하려면 setter가 필요한데 이렇게 이용할바엔 세터 주입 방식을 사용하기에 권장하는 방식은 아님

 

4.일반 메서드 주입 -> 말 그대로 일반 메서드를 통해서 주입을 받을 수 있고 한번에 여러 필드를 주입 받을 수 있지만, 생성자 주입과 수정자 주입에서 거의다 해결하기 때문에 일반적으로 사용 안함

 

일반적으로 생성자 주입을 많이 하고 필요에따라 수정자 주입을 하는 편이다. 생성자 주입을 하면 순수 자바 코드로 테스트하기 용이하고 final을 사용할 수 있다는게 장점이다. final을 사용하게 되면 생성자에서 값을 넣을 수 있고 생성자에서 코드를 누락했다! 하면 바로 오류를 알려줌 ( 컴파일 오류는 착한거야 ㅠㅠ)

 

생성자 주입 방식 같은 경우 코드가 조금 길어지는데 이때 롬북 방식을 사용해서 해결이 가능하다!

 

생성자 주입을 해야하는 이유로는 대부분의 의존관게는 의존관게를 변경할 일이 거의 없고 수정자 주입을 사용하려면 setter 메서드를 public으로 열어야 하는데 변경 가능성이 좋지 못하다.

 

 

주입할 스프링 빈이 없어도 동작 해야할때

3가지 방법이 있다.

package hello.core.autowired;

import hello.core.member.Member;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.lang.Nullable;

import java.lang.annotation.Annotation;
import java.util.Optional;

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);
        }
        @Autowired
        public void setNoBean2(@Nullable Member noBean2){
            System.out.println("noBean2 = " + noBean2);

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

        }
    }
}

위 테스트 코드 실행 결과

위 코드는 빈이 들어오지 않았을때 처리하는 3가지 방법을 테스트한것이다.

첫번째는 @Autowired가 옵션이 기본적으로 true로 되어있어서 옵션을 false로 바꾼것이다. 이 방법은 주입할 대상이 없으면  호출 자체가 안된다.

두번째는 @Nullable을 이용한 방식으로 주입할 대상이 없으면 null이 호출된다.

세번째 방식으로 Optional<> 방식이 있는데 주입할 대상이 없으면 Optional.empty가 호출된다.

 

그리고 위에 말했던 롬북을 사용하게 되면 코드가 많이 줄어든다.(라이브러리를 추가하면 된다.)

// 롬북 사용전
@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
  @RequiredArgsConstructor
  public class OrderServiceImpl implements OrderService {
      private final MemberRepository memberRepository;
      private final DiscountPolicy discountPolicy;
}

라이브러리 설치하면 @RequiredArgsConstructor 어노테이션만 붙여주면 된다. 그럼 코드가 매우 깔끔해진다.

 

만약 조회 빈이 2개 이상일경우 어떻게 될까?

만약 같은 타입인 빈이 2개 있으면 NoUniqueBeanDefinitionException 에러가 발생한다. 하위 타입으로 지정할 수 도 있지만 이건 DIP 위반이다.( 구현체의 의존하기 때문 )

그러면 이에 대한 해결방법으로는 

@Autowired는 타입으로 조회를 한다 그리고 만약 여러개의 타입있으면 필드 이름, 파라미터 이름으로 찾아서 같은 이름이 있는 것을 매칭한다.

또 다른 방법으로는 @Qualifier를 사용할 수 있다. 이 어노테이션은 빈 이름을 지정하는 것이 아닌 별명을 붙여주는 거라고 생각하는게 나은거 같다.

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

이렇게 되면 @Qualifier 어노테이션이 붙어있으면 @Qualifier가 붙은 클래스를 찾은뒤에 뒤에 구분자(이름)가 같은 것을 찾아서 그것을 주입해준다. (수동 빈 등록할때도 사용 가능)

 

제일 많이 쓰이는 기능으로는 @Primary가 있다. 우선권을 원하는 구현체에 @Primary 어노테이션을 붙여주기만 하면된다.

 

그럼 @Primary와 @Qualifier 둘 중에 뭐가 더 우선순위 인가 궁금 할 수 도 있다.

일단 정답은 @Qualifier가 우선순위가 더 높다. 스프링은 자동 보다는 수동, 넓은 범위 보다는 좁은 범위의 선택이 우선순위가 더 높다.

 

@Qualifier를 사용할때 단점아닌 단점 하나는 문자를 적을때 입력할때 컴파일 오류 체크가 확인이 안된다.(실행을 해야 알 수 있음) 이 점을 해결하기 위해서 어노테이션을 만들어서 사용할 수 도 있다.

  package hello.core.annotataion;
  import org.springframework.beans.factory.annotation.Qualifier;
  import java.lang.annotation.*;
  @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
  ElementType.TYPE, ElementType.ANNOTATION_TYPE})
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  @Qualifier("mainDiscountPolicy")
  public @interface MainDiscountPolicy {
  }

이렇게 어노테이션을 만들고 위에 @Qualifier을 붙이면 이제 부터 이 어노테이션이 Qualifier역할을 할 수 있고, 만약 어노테이션 이름이 틀릴시에는 바로 컴파일 오류 체크를 할 수 있다.

 

요즘엔 자동을 선호하는 추세이다 그럼 수동은 언제 사용할까?

보통 업무 로직은 자동 기능을 적극적으로 사용하는게 좋고 기술 지원은 수동 빈을 사용해서 등록을 명확하게 드러내는게 좋다.

 

또 등록된 빈을 Map이나 리스트로 보여줄 수 있다.

728x90