스프링 기본 section8

Sean·2025년 5월 16일

의존관계 자동 주입

의존관계 주입 크게 4가지 : 생성자, 수정자(setter), 필드, 일반 메서드

생성자 주입

  • 생성자 호출시점 딱 1번만 호출 보장.
  • 불변, 필수 의존관계에서 사용
  • 생성자 1개만 있으면 @Autowired 생략 가능

수정자 주입(setter)

@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
	this.memberRepository = memberRepository;
}

@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
	this.discountPolicy = discountPolicy;
}
  • @Autowired 기본 동작 -> 주입 대상 없으면 error. 주입 대상 없어도 동작하려면 @Autowired(required = false)

필드 주입

  • 사용X
  • 스프링 설정 목적 가진 @Configuration 같은 곳에서만 특별 용도로 사용
@Component
public class OrderServiceImpl implements OrderService {
	
    @Autowired
	private MemberRepository memberRepository;
	@Autowired
	private DiscountPolicy discountPolicy;
}
  • 순수 자바 코드에선 @Autowired 동작X. 스프링 컨테이너를 통합한 경우만 가능.

일반 메서드 주입

  • 한번에 여러 필드 주입 가능
  • 잘 사용 안함.
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy
discountPolicy) {
	this.memberRepository = memberRepository;
	this.discountPolicy = discountPolicy;
}

옵션 처리

주입할 빈 없어도 동작해야 할 때가 있음. 아까 알아보았듯, @Autowired(required=false) 처럼 옵션 처리하는 여러 방법들 존재.

  • @Autowired(required=false) : 자동 주입 대상 없으면 수정자 메서드 자체가 호출 안됨.
  • @Nullable : 자동 주입 대상 없으면 null 입력됨
  • Optional<> : 자동 주입 대상 없으면 Optional.empty 입력됨
옵션처리 예제
//호출 안됨
@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);

생성자 주입을 선택하자.

불변

  • 대부분의 의존관계 주입은 한 번 일어나면 종료 시점까지 의존관계 변경할 일 없음. 오히려 변하면 안된다.
  • 수정자 주입 사용하려면 setter를 public으로 열어야함 -> 변경으로인한 문제 발생 가능성.

@@강의 확인

누락

set으로 하면,

final 키워드 사용가능

final 사용 가능 이점 : 생성자에서 값 설정 안된 오류를 컴파일 시점에서 막아줌.

참고 : 수정자 주입 포함한 나머지 방식은 생성자 이후 호출이므로, final 키워드 사용 불가능.
오직 생성자 주입만 가능.

정리
  • 생성자 주입 사용 이유 : 프레임워크 의존 않고, 순수한 자바 특징 잘 살리는 방법
  • 기본으로 생성자, 필수 값 아닌 경우 수정자 가능. 동시 사용 가능
  • 항상 생성자 쓰는걸로 하고, 가끔 옵션 필요시 수정자. 필드 주입 X.

Lombok

생성자 만들기, 주입 받은 값 대입, -- -> 간편한 방법 찾기 -> Lombok

@RequiredArgsConstructor -> final 붙은 필드 모은 후 생성자 자동 생성.

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;
}

조회 빈 2개 이상인 경우.

@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
@Autowired
private DiscountPolicy discountPolicy

-> NoUniqueBeanDefinitionException 오류 발생

@Autowired 필드 명, @Qualifier, @Primary

  • @Autowired 필드 명 매칭
  • @Qualifier -> @Qualifier 끼리 매칭 -> 빈 이름 매칭
  • @Primary 사용

@Autowired 필드 명 매칭

@Autowired

1.타입 매칭
2.타입 매칭 결과 2개 이상일 시 필드 명, 파라미터 명으로 빈 이름 매칭

원래 코드
@Autowired
private DiscountPolicy discountPolicy	//Rate, Fix 총 두 개라 못 정함.
변경 후 코드
@Autowired
private DiscountPolicy rateDiscountPolicy	//필드명으로 확정지어줌.

필드 명 rateDiscountPolicy로 정상 주입.

@Qualifier

빈 등록시 @Qualifier를 붙여 준다.

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

생성자 자동 주입 예시

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

수정자 자동 주입 예시

@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier("mainDiscountPolicy")
DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}

@Qualifier("mainDiscountPolicy") 못찾으면 -> mainDiscounPolicy 이름의 스프링 빈을 찾으러 감.
이렇게 빈까지 뒤져가며 찾아주지만, @qualifier@qualifier 찾는 용도로만 사용하는게 명확.

@Qualifier 정리
1. @Qualifier끼리 매칭
2. 빈 이름 매칭
3. NoSuchBeanDefinitionException 예외 발생

@Primary

우선순위를 정하는 방법. @Autowiried에 여러 빈 나올 시, @Primary 붙은게 우선권 가짐.

사용 예시

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
public class FixDiscountPolicy implements DiscountPolicy {}

@Primary, @Qualifier 뭘 써야하나.

@Qualifier 단점은 주입받을 때 모든 코드에 붙여줘야함. 반면 @Primary는 그럴 필요 X.

@Primary, @Qualifier 활용
메인으로 쓰이는 빈은 @Primary@Qualifier 없이 편리하게 조회,
서브로 쓰이는 빈은 @Qualifier로 명시적으로 획득하는 방식으로 사용.

우선순위?

스프링은 자동보다는 수동이, 넓은 범위 선택권 보단 좁은 범위 선택권이 우선 순위 높음. 그러므로 여기에서도 @Qualifier가 우선권 높음.

어노테이션 직접 만들기

@Qualifier("mainDiscountPolicy") 처럼 문자를 적으면 컴파일 때 체크 안됨(오타 나도 문제 모름)-> 컴파일러가 안잡아줘서 실행 중 문제 갈생 가능.
-> 어노테이션 만들어서 해결 가능.

@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

List, Map으로 받은 후, key값을 이용하여 변수에 저장.

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);
	}
}
profile
저스트두잇

0개의 댓글