반응형

 

1. 의존관계 주입 방법 4가지
생성자 주입

- 생성자를 통해서 의존 관계를 주입 받음.

- 생성자 호출시점에 딱 1번만 호출되는 것이 보장.
- 불변, 필수 의존관계에 사용

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

 

수정자 주입(setter 주입)

- setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입 받음.

- 선택, 변경 가능성이 있는 의존관계에 사용
- 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법

※ @Autowired 의 기본 동작은 주입할 대상이 없으면 오류가 발생.

주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.

 

필드 주입

- 코드가 간결 하지만 외부에서 변경이 불가능해서 테스트 하기 힘듦
- DI 프레임워크가 없으면 아무것도 할 수 없음.

- 사용 안하는것을 추천


일반 메서드 주입

- 일반 메서드를 통해서 주입 받음.
- 한번에 여러 필드를 주입 받을 수 있음, 일반적으로 잘 사용하지 않는다.

 

※ 주입할 스프링 빈이 없어도 동작해야 할 때 옵션 사용법

package hello.core.autowired;

import java.util.Optional;

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 hello.core.member.Member;

public class AuotowiredTest {
	
	@Test
	void AutowiredOption() {
		ApplicationContext ac =  new AnnotationConfigApplicationContext(TestBean.class);
		
	}
	
	static class TestBean{		
/*		// 오류남
		@Autowired
		public void setNoBean0(Member noBean0) {
			System.out.println("noBean0 = "+ noBean0);
		}
 */
		
		// 호출 자체가 안됨
		@Autowired(required = false)
		public void setNoBean1(Member noBean1) {
			System.out.println("noBean1 = "+ noBean1);
		}
		
		// 호출은 되지만 null
		@Autowired
		public void setNoBean2(@Nullable Member noBean2) {
			System.out.println("noBean2 = "+ noBean2);			
		}
		
		// 호출 되고 Optional.empty로 들어옴
		@Autowired
		public void setNoBean3(Optional<Member> noBean3) {
			System.out.println("noBean3 = "+ noBean3);			
		}
	}

}

 

생성자 주입을 사용해야 함

- 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안됨.(불변)

- 수정자 주입을 하게 되면 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것 자체가 안좋은 설계.

- 생성자 주입을 사용하면 필드에 final 키워드를 사용가능, 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아줌.

- 생성자 주입이 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법

 

※ Lombok

@RequiredArgsConstructor어노테이션을 붙이면 final이 붙은 필드에 자동으로 생성자 주입을 시켜줌.

예시)

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;
}

 

 

2. 의존관계 주입 시 조회한 빈이 2개 이상일 경우

@Autowired 는 타입(Type)으로 조회 하는데 조회된 타입이 2개일 경우 NoUniqueBeanDefinitionException 오류가 발생.

 

해결 방법 

1) @Autowired 필드 명 매칭 

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

예시)

private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
	
	
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
	this.memberRepository = memberRepository;
	this.discountPolicy = rateDiscountPolicy;
}

 


2) @Qualifier끼리 매칭 빈 이름 매칭

- @Qualifier 는 추가 구분자를 붙여주는 방법. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것이 아님.

예시)

조회된 첫번째 빈에 @Qualifier 적용

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

 

조회된 두번째 빈에 @Qualifier 적용

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

 

@Qualifier 사용

private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;

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

 

 

3) @Primary 사용

- @Primary 는 우선순위를 정하는 방법. @Autowired 시에 여러 빈이 매칭되면 @Primary 가 우선권을 가짐.

예시)

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy { ... }



@Component
public class FixDiscountPolicy implements DiscountPolicy { ... }

 

※ 우선순위
@Primary 는 기본값 처럼 동작하는 것이고, @Qualifier 는 매우 상세하게 동작한다. 

스프링은 자동보다는 수동이, 넓은 범위의 선택권 보다는 좁은 범위의 선택권이 우선 순위가 높다. 여기서도 @Qualifier 가 우선권이 높다.

 

 

3. 조회한 빈이 모두 필요할때 (List, Map)

예제)

package hello.core.autowired;

import java.util.List;
import java.util.Map;

import org.assertj.core.api.Assertions;
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 hello.core.AutoAppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Grade;
import hello.core.member.Member;

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");
	 
		Assertions.assertThat(discountService).isInstanceOf(DiscountService.class);
		Assertions.assertThat(discountPrice).isEqualTo(1000);
		
		int rateDiscountPrice =  discountService.discount(member, 20000, "rateDiscountPolicy");
		Assertions.assertThat(rateDiscountPrice).isEqualTo(2000);
	}
	
	
	static class DiscountService{
		// DiscountPolicy의 2개의 구현체 자동 주입
		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) {
			// discountCode로 주입된 구현체 선택
			DiscountPolicy discountPolicy = policyMap.get(discountCode);
			return discountPolicy.discount(member, price);
		}
	}
}

 

 

4. 수동빈과 자동빈의 선택

- 편리한 자동 기능을 기본으로 사용.
- 직접 등록하는 기술 지원 객체는 수동 등록
- 다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민

반응형

+ Recent posts