의존 관계 자동 주입

tyghu77·2023년 6월 22일
0
post-custom-banner

의존관계 주입 방법

  1. 생성자 주입
    생성자를 통해 의존관계를 주입받는 방법
    생성자 호출 시점에 딱 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;
    }
    ...

생성자 주입은 다른 방법과는 달리 스프링 빈이 등록될 때 자동 주입이 일어난다.
2. 수정자 주입(setter 주입)
"선택, 변경" 가능성이 있는 의존관계에 사용된다.
스프링 빈에 등록이 안되어 있을 때도 주입할 수 있다. (주입할 대상이 없어도 동작할 수 있음)
(잘 하지는 않지만 중간에 구현체를 바꾸고싶다면 외부에서 호출함으로써 변경 가능하다.)

@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;
    
    @Autowired(required = false)
    public void setMemberRepository(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
	}
    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }
	...
  1. 필드 주입
    말 그대로 필드에 값을 넣는 것.
    사용하지 말 것
    외부에서 변경이 불가능해서 테스트하기 힘들다는 치명적인 단점이 있다.
    DI 컨테이너가 없으면 테스트를 할 수 없음.
    어플리케이션의 실제 코드와 관계 없는 테스트 코드나,
    스프링 설정을 목적으로 하는 @Configuration같은 곳에서만 특별한 용도로 사용한다.
    테스트에서만 가끔 쓰고 어플리케이션에서는 사용하지 말 것
@Component
public class OrderServiceImpl implements OrderService {

    @Autowired private MemberRepository memberRepository;
    @Autowired private DiscountPolicy discountPolicy;
	...
  1. 일반 메서드 주입
    일반 메서드를 통해 주입 받을 수 있다.
    한번에 여러 필드를 주입 받을 수 있다.
    일반적으로 잘 사용하지 않는다.
@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;
    }
    ...

자동 의존관계 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다.

옵션 처리

  1. @Autowired(required=false)
    자동 주입할 대상이 없으면 메서드 자체가 호출 안됨
  2. org.springframework.lang.@Nullable
    자동 주입할 대상이 없으면 null이 입력된다.
    Optional<>
    자동 주입할 대상이 없으면 Optional.empty가 입력된다.
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//null
        public void setNoBean2(@Nullable Member noBean2) {
            System.out.println("noBean2 = " + noBean2);
        }
        @Autowired//Optional.empty
        public void setNoBean3(Optional<Member> noBean3) {
            System.out.println("noBean3 = " + noBean3);
        }
    }
}

어떤 자동 주입을 사용해야할까

생성자 주입을 사용해라

  1. 불변
    대부분의 의존 관계는 앱 종료 전까지 변하면 안된다.
    수정자 주입을 사용하게되면 set함수를 public으로 열어둬야 하기때문에 누군가 변경할 수 있다.
    생성자 주입은 객체 생성 시 딱 딱 1번만 호출되기 때문에 불변하게 설계할 수 있다.

  2. 누락
    수정자 주입을 사용할 경우 프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우 의존 관계를 자세히 알 수 없다.(알려면 직접 코드를 봐야한다)

  3. final 키워드를 사용할 수 있다.
    final은 생성할 때 정해지면 바뀌지 않고, 직접 값을 넣거나 생성자에서만 값을 줄 수 있다.
    그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.

롬복 사용

최근에는 생성자를 딱 1개 두고 @Autowired를 생략하는 방법을 주로 사용한다. 여기서 롬복을 사용하면 코드가 간결해진다.
롬복이 자바의 어노테이션 프로세서라는 기능을 사용하여 컴파일 시점에 생성자 코드를 자동으로 생성해준다.

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

//    @Autowired //LOMBOK @RequiredArgsConstructor가 대신 써주는 코드
//    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
//        this.memberRepository = memberRepository;
//        this.discountPolicy = discountPolicy;
//    }
    ...

Autowired할 때 bean이 두개 이상인 경우

이름만 다르고, 완전히 똑같은 스프링 빈이 2개 있을 때 해결방법은 다음과 같다.

  1. Autowired 필드 명 매칭
    @Autowired는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
@Autowired
private DiscountPolicy discountPolicy

아래 코드로 바꿔주면 정상으로 돌아간다.

@Autowired
private DiscountPolicy fixDiscountPolicy
  1. @Qualifier 끼리 매칭
    같은 Qualifiler를 찾아서 매칭해준다.
@Component
@Qualifier("mainDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy { ... }
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
    ...

만약에 Qualifier 이름을 못찾으면 그 이름의 스프링 빈을 추가로 찾는다.

  1. @Primary 사용 (자주 사용)
    @Primary는 우선순위를 정하는 방법이다. @Autowired 시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.
    @Primary와 @Qualifier가 있으면 @Qualifier가 더 우선순위가 높다.

어노테이션 직접 만들어 사용하기

@Qualifier("xxx")를 사용할 때, 문자는 컴파일에서 체크할 수 없다.(ex 오타) 어노테이션을 직접 만들어서 사용하면 경고가 뜨기때문에 오류를 찾기 쉽다.

수동 빈 등록은 언제 사용하면 좋을까

기술 지원 로직들은 수동 빈 등록을 사용하여 명확하게 들어내는 것이 좋다.

profile
배운것을 기록하자
post-custom-banner

0개의 댓글