다양한 의존관계 주입 방법

  • 생성자 주입: 한 번만 실행되므로, 누락을 방지할 수 있고 불변(final)함. 생성자가 하나면@Autowired를 생략 가능.
  • 세터 주입: 세터 메서드에 @Autowired를 통해 자동 주입. 선택, 변경 가능성이 있는 의존관계에 사용하지만 거의 X
  • 필드 주입: 필드에 @Autowired를 통해 바로 필드에 자동 주입. 외부에서 변경이 불가능해서 테스트 하기 힘듦.
  • 일반 메서드 주입: init 등의 초기화 함수에 @Autowired를 통해 일반 메서드로도 주입하는 것. 한 번에 여러 필드에 의존관계를 자동 주입 가능. 굳이 사용 X

 

 

옵션 처리

  • @Autowired의 기본 값인 required = true가 되어있으면, 등록된 스프링 빈이 없을 때 오류가 발생한다.
  • 그래서 required = false로 설정함으로써, 자동 주입할 대상이 없으면 메서드 자체가 호출이 안되게 한다.
  • 매개변수에 @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
        public void setNoBean2(@Nullable Member noBean2) {
            System.out.println("nobean2 = " + noBean2);
        }

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

 

 

 

롬복

  • Getter, Setter, 생성자 등을 어노테이션(@Getter, @Setter 등)을 통해 자동 생성해주는 라이브러리.
  • @RequiredArgsConstructor를 통해 final 필드를, 매개변수로 받아서 생성자를 통해 주입해주는 생성자를 생성해줌.
  • build.gradle에 롬복 설정을 추가해준다. 스프링을 처음 초기화 할 때 롬복 라이브러리를 추가해서 만들어도 된다.
  • 인텔리제이에 롬복 플러그인을 설치하고, File->Settings에서 Annotation Processors 기능을 활성화 한다.

 

 

Autowired 매칭

  • 타입 매칭: 해당 타입의 빈이 하나만 등록되어 있으면 그대로 가져다 씀
  • 반면 타입 매칭의 결과가 2개 이상이라면, 파라미터 명으로 빈 이름을 매칭.
  • @Qualifer를 통해서도 가능. @Qualifer는 @Qualifer를 찾는 용도로만 사용하는게 명확하고 좋다.
  • @Primary를 통해, 해당 어노테이션이 달린 클래스를 우선적으로 매칭. 이거는 많이 씀.
  • 우선순위는 @Qualifer가 @Primary보다 높다. @Qualifer가 더 상세하기 때문.
// 파라미터 명으로 빈 이름 매칭
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = rateDiscountPolicy;
}



// ------------------------@Qualifer로--------------------------------------



@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy{

    private int discountFixAmount = 1000;

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return discountFixAmount;
        }
        else {
            return 0;
        }
    }
}



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

    private int discountPercent = 10;

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return price * discountPercent / 100;
        }
        else {
            return 0;
        }
    }
}



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



// ---------------------------@Primary------------------------------


// DiscountPolicy 빈이 두 개 이상 찾아지면, Primary인 RateDiscountPolicy가 매칭됨.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {

    private int discountPercent = 10;

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return price * discountPercent / 100;
        }
        else {
            return 0;
        }
    }
}

 

 

 

 

어노테이션 직접 만들기

  • 무분별하게 사용하면 유지보수에 혼란 유발 가능.
package hello.core.annotation;

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)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}

 

 

 

 

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

  • 클라이언트가 할인의 종류(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);

        int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");
        assertThat(rateDiscountPrice).isEqualTo(2000);
    }

    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;

            System.out.println("policyMap = " + policyMap);
            System.out.println("policies = " + policies);
        }

        public int discount(Member member, int price, String discountCode) {
            DiscountPolicy discountPolicy = policyMap.get(discountCode);

            return discountPolicy.discount(member, price);
        }
    }
}

 

 

 

 

자동, 수동의 올바른 실무 운영 기준

  • 업무 로직(비즈니스 로직) 빈 자동 기능을 적극 사용하는 것이 좋다. 컨트롤러, 서비스처럼 계층이 명확하고 그 수가 많고, 보통 문제가 발생하기 명확하게 파악하기 쉽기 때문.
  • 기술 지원 빈(공통 기술, AOP, DB 커넥션 등)수동 빈 등록을 사용하는 것이 좋다. 업무 로직에 비해 수가 매우 적고, 애플리케이션 전반에 걸쳐 영향을 미치기 때문. 적용이 잘 되고 있는지 명확하게 보이지 않기 때문.
  • DiscountPolicy 처럼 어떤 빈이 들어올지 파악하기 위해서 여러 코드를 찾아봐야 하는 경우 수동 빈으로 등록하거나, 자동으로 할려면 특정 패키지에 같이 묶어두는게 좋다.
  • 스프링 부트가 아니라 내가 직접 기술 지원 객체를 스프링 빈으로 등록한다면 수동으로 등록해서 명확하게 드러내는 것이 좋다.
// 자동 등록을 사용하지만, 같은 패키지로 묶어서 관리
@Configuration
 public class DiscountPolicyConfig {
    
    @Bean
    public DiscountPolicy rateDiscountPolicy() {
    	return new RateDiscountPolicy();
    }
    @Bean
    public DiscountPolicy fixDiscountPolicy() {
    	return new FixDiscountPolicy();
    }
 }