Spring 강의/springMVC

sprongMVC 기본 - (7) 의존관계 자동 주입

lxexjx 2022. 3. 12. 21:53

[의존관계 주입 방법]

 

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

OrderServiceImpl이 스프링 빈에 등록될때 생성자 호출시 @Autowired를 보고 스프링 컨테이너에서 스프링 빈을 꺼내서(memberRepository랑 discountPolicy) 를 주입. 중간에 바꿀 수 없어(불변). final은 값이 필수로 있어야된다는 것(필수)

 

 

 

2.수정자 주입(setter 주입) - 선택, 변경 가능성이 있는 의존관계에 사용

@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;
    
    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }
}

OrderServiceImpl이 스프링 빈에 등록될때 @Autowired를 보고 자동 주입. 

 

 

3.필드 주입 - 필드에 바로 주입.거의 테스트 코드에서만 사용

@Component
public class OrderServiceImpl implements OrderService {

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

 

 

4.일반 메서드 주입 - 아무 메서드에 

 

 

생성자 주입 선택!

대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.) 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 한다. 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다. 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.

 


 

[자동 주입 대상 옵션처리]

 

@Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨

org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 호출은 되나 null이 입력

Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력

 

<AutowiredTest>

static class TestBean
    //호출 안됨
    @Autowired(required = false)
    public void setNoBean1(Member member) {		//관리되는 스프링빈이 없게함
        System.out.println("setNoBean1 = " + member);
    }
    //null 호출
    @Autowired
    public void setNoBean2(@Nullable Member member) {
        System.out.println("setNoBean2 = " + member);
    }
    //Optional.empty 호출
    @Autowired(required = false)
    public void setNoBean3(Optional<Member> member) {
        System.out.println("setNoBean3 = " + member);
    }

 

 


 

[롬복] - Getter,Setter자동으로 만들어주고 생성자, ToString 관련 지원

@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 : 필수값 final을 가지고 있는 파라미터로 받는 생성자를 그대로 만들어줘

생성자 안보일뿐 존재.

 

 


 

 

[조회 대상 빈이 2개 이상일 때 해결 방법]

 

1.@Autowired 필드 명 매칭 : @Autowired 는 타입 매칭을 시도하고 여러 빈이 있으면 필드 이름이나 파라미터 이름으로 빈 이름을 추가 매칭, 본인과 같은 타입이거나 자식들 가 가져와서 

<OrderServiceImpl>

@Autowired
private DiscountPolicy discountPolicy  ->  private DiscountPolicy rateDiscountPolicy로 변경

 

 

2.@Qualifier 어노테이션 사용 : @Qualifier끼리 매칭-> 빈 이름 매칭 - 추가 구분자를 붙여주는 방법. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아님

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

@Qualifier 의 단점은 주입 받을 때 다음과 같이 모든 코드에 @Qualifier 를 붙여주어야됨

빈 직접 등록시에도 @Qualifier사용 가능.

 

 

3.@Primary 사용 : 여러 빈이 매칭되면 @Primary 가 우선권을 가짐

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
public class FixDiscountPolicy implements DiscountPolicy {}

@Primary 를 사용하면 이렇게 @Qualifier 를 붙일 필요가 없음.

@Primary보다 @Qualifier 가 우선권이 높음(더 자세한 것이 우선권이 높음)

 


 

[애노테이션 직접 만들기]

<MainDiscountPolicy어노테이션>

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

직접 만들고

 

<RateDiscountPolicy>

@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}

 

가져다 쓰는데 <OrderServiceImpl>

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}
//수정자 자동 주입
@Autowired
public DiscountPolicy setDiscountPolicy(@MainDiscountPolicy DiscountPolicy discountPolicy) {
    return discountPolicy;
}

 


 

[조회한 빈이 모두 필요할 때, List, Map] - 의도적으로 정말 해당 타입의 스프링 빈이 다 필요한 경우

ex)클라이언트가 할인의 종류를 선택할 수 있을 때(rate,fix)

 

<AllBeanTest>

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);
    }
    
    static class DiscountService {
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policies;
        
        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);
            
            System.out.println("discountCode = " + discountCode);
            System.out.println("discountPolicy = " + discountPolicy);
           
            return discountPolicy.discount(member, price);
        }
    }
}