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

강아람·2022년 8월 27일
0
post-thumbnail

📚 다양한 의존관계 주입 방법

1) 생성자 주입
2) 수정자 주입(setter 주입)
3) 필드 주입
4) 일반 메서드 주입


📖 생성자 주입

생성자를 통해 의존관계를 주입하는 방법

  • 생성자를 호출하는 시점에 의존관계가 만들어지기 때문에 외부에서 의존관계를 수정하는 것이 불가능하다. (🌟 불변)
  • 불변, 필수 의존관계에 사용
  • 생성자가 딱 1개만 있을 때에는 @Autowired 생략 가능

📖 수정자 주입

필드의 값을 변경하는 수정자 메서드를 통해 의존관계를 주입하는 방법
💻 스프링 컨테이너의 life cycle

  • 스프링 빈 등록
  • 의존관계(연관관계) 등록 - @Autowired

📖 필드 주입

필드에 바로 주입하는 방법

  • 외부에서 변경이 불가능하기 때문에 테스트가 힘들다.
  • DI 프레임워크가 없으면 사용할 수 없음
  • 특수한 목적에만 사용
    • 테스트 코드나 스프링 설정을 위한 @Configuration 등

📖 일반 메서드 주입

일반 메서드를 통해 주입하는 방법

  • 한번에 여러 필드를 주입받을 수 있다.

💡 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다. 스프링 빈이 아닌 클래스에서 @Autowired는 동작하지 않는다.




📚 옵션 처리

👀 주입할 스프링 빈이 없어도 동작해야 할 때,
@Autowired를 사용하면 required 옵션의 기본값이 ture로 되어있기 때문에 자동 주입 대상이 없으면 오류가 발생한다.

📖 자동 주입대상 옵션 처리 방법

1) @Autowired(required=false): 자동 주입할 대상이 없으면 수정자 메서드 호출 X
2) org.springframework.lang.@Nullable: 자동 주입할 대상이 없으면 null이 입력됨
3) Optional<>: 자동 주입할 대상이 없으면 Optional.empty가 입력됨

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);
    }
}

실행 결과 : ApplicationContext를 생성한 결과!

  • setNoBean1 메서드 자체가 호출되지 않았음을 알 수 있다.



📚 생성자 주입을 선택해라!

👀 생성자 주입을 권장하는 이유

◾ 불변

  • 대부분의 의존관계는 애플리케이션 종료 전까지 불변해야 한다.
  • 수정자 주입을 사용하면 public 메서드로 열어두어야 하기 때문에 누군가 실수로 변경해서는 안되는 것을 변경할 수 있다.

◾ 누락

  • 수정자 주입의 경우에는 의존관계 주입이 누락될 수 있으므로 생성자 주입을 사용하여 의존관계 누락 시 컴파일 에러로 확인할 수 있도록 한다.

final 키워드

  • 생성자 주입을 사용하면 필드에 final로 생성자에서 값이 설정되지 않은 에러를 컴파일 시점에 파악할 수 있다.



📚 롬복과 최신 트렌드

✅롬복 라이브러리


📖 롬복(Lombok)을 사용하여 코드 최적화

  • 이전 코드
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

  • 최적화 : Lombok 라이브러리의 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드들을 주입하는 생성자를 자동으로 만들어 준다.
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
}

💡 롬복이 자바의 애노테이션 프로세서라는 기능을 이용해 컴파일 시점에 생성자 코드를 자동으로 생성해준다.




📚 빈이 2개 이상 조회되는 문제

@Autowired는 타입으로 빈을 조회하기 때문에 타입으로 조회된 빈이 2개 이상일 때 문제가 발생하게 된다.

📖 @Autowired 필드명 매칭

  1. 타입 매칭
  2. 타입 매칭의 결과가 2개 이상일 때 필드명, 파라미터명으로 빈 이름 매칭
@Autowired
private DiscountPolicy rateDiscountPolicy

스프링 빈에 DiscountPolicy의 하위 타입으로 RateDiscountPolicyFixDiscountPolicy가 있기 때문에 두 개의 빈이 조회된다. 이때 필드명이 rateDiscountPolicy일 경우 두 개의 타입 중 RateDiscountPolicy를 선택해 의존관계를 주입한다.


📖 @Qualifier

추가 구분자를 붙여주는 방법

  1. @Qualifier 간 매칭
  2. 구분자와 같은 이름의 스프링 빈 매칭
  3. 없을 경우 NoSuchBeanDefinitionException 예외 발생

빈 등록 시

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}

생성자 자동 주입 시

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

📖 @Primary

우선순위를 부여하여 우선권을 가지는 빈 선택

  • rateDiscountPolicy에게 우선권 부여
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}

💻 @Primary@Qualifier 활용

메인 데이터베이스의 커넥션을 획득하는 스프링 빈의 경우 @Primary를 적용하여 조회하는 곳에서 수정없이 편리하게 조회할 수 있도록 하고, 서브 데이터베이스의 커넥션을 획득하는 스프링 빈의 경우 @Qualifier를 지정해서 명시적으로 획득하는 방식으로 사용한다.

💻 우선순위

@Primary는 기본값처럼 동작하고, @Qualifier는 매우 상세하기 동작하기 때문에 @Qualifier에게 우선권이 부여된다.




📚 애노테이션 직접 만들기

📖 @Qualifier를 사용하는 애노테이션 만들기

위의 @Qualifier 애노테이션을 사용하는 애노테이션을 만들어보자.

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {}

  • 애노테이션 사용
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}

  • 생성자 자동 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}



📚 조회한 빈이 모두 필요할 때, List, Map

static class DiscountService {
    private final Map<String, DiscountPolicy> policyMap;
    private final List<DiscountPolicy> policies;

    @Autowired
    public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
        this.policyMap = policyMap;
        this.policies = policies;
    }
}

💻 DiscountService의 생성자가 호출되면 policyMappolicies가 자동 생성되어 주입된다. 이 맵과 리스트에는 스프링 빈에 저장된 DiscountPolicy 타입의 빈들이 들어간다.

0개의 댓글