Spring

Spring Basic- DI(의존 관계) 자동 주입 2

녁이 2023. 5. 22. 00:07
728x90
반응형

2023.05.19 - [SpringBoot] - Spring Basic- DI(의존 관계) 자동 주입

 

Spring Basic- DI(의존 관계) 자동 주입

DI(의존관계 주입)에는 크게 4가지가 있다. 1. 생성자 주입 2. 수정자 주입 3. 필드 주입 4. 일반 메서드 주입 1. 생성자 주입 생성자(constructor)를 통해서 DI를 주입하는 방법 1번만 호출되는 것을 보장

junhyuk-develop.tistory.com

 

주제 : @Autowired를 통해 DI를 주입하는 데에 있어서 여러 가지 상황과 이에 대한 옵션 처리


옵션 처리

 

@Autowired만 사용하면 required 옵션의 default값이 true로 되어 있기 때문에 자동 주입 대상이 없으면 오류가 발생!

BUT, 주입할 스프링 빈이 없어도 동작해야 할 때가 있다.

 

그러한 경우들을 알아보자.

  • @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨
  • org.springframework.lang.@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);
}

// 예시를 들기 위해 setNobean1,2,3 를 임시로 만듬

// 여기서, Optional은 java 문법으로 사용되고 있다.

// @Nullable, Optional은 스프링 전반에 걸쳐 지원됨

 

출력결과

setNoBean2 = null
setNoBean3 = Optional.empty

생성자 주입이 짱!

 

여러가지 이유가 있겠지만, 최근에는 많은 프레임워크가 생성자 주입을 권장한다고 한다.

크게 불변과 누락 등의 이유가 있다. 

간단하게 설명하자면, 생성될 때 호출이 되고 이후에 변경될 필요가 없다는 것과 다른 방법을 쓰다보면 DI가 누락될 수 있다는 점이 있다.

또한, "final"을 사용할 수 있다는 장점이 있다.         // final 키워드는 값이 무조건 있어야함

그래서 생성자에서 혹시나 값이 설정되지 않는 오류가 발생한다면, 이를 컴파일 시점에서 발견할 수 있다.

[컴파일 오류는 개발자가 오류를 잡기 편하기때문에 너무 좋은 것..]

 

결론 : 앞으로 기본적으로는 생성자 주입을 사용하면서, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하자.

 


롬복

 

앞서 말한대로 생성자 주입을 사용하기 위해 생성자, 주입 받을 값을 대입하는 코드 등을 만들어야 한다.

이를 더 최적화하고 짧고 간결하게 코딩할 수는 없을까?

그 해답은 바로 "롬복"이다.

이번에도 OrderServiceImpl 클래스의 일부 코드를 예시로 들어보자.

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

// 생성자가 하나라서 @Autowired 생략함.

이 코드에서 롬복을 사용하면 얼마나 간결해질지 확인해보자.

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;

}

롬복 라이브러리가 제공하는 @RequiredArgsConstructor는 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다.

이는 자바의 애노테이션 프로세스라는 기능을 이용한 것인데, 컴파일 시점에서 생성자 코드를 생성해준다.

※ 롬복 라이브러리를 적용하기 위해선 build.gradle에 라이브러리 및 환경 추가를 해줘야 한다.  [방법은 구글링해보자..]


조회 빈이 2개 이상일때

 

@Autowired는 타입(Type)으로 조회한다.

스프링 빈을 조회했을 때, 이 타입으로 조회가 되면 선택된 빈이 2개 이상일 때 문제가 발생한다.

예를 들어, DiscountPolicy 라는 인터페이스 클래스 하위로 FixDiscountPolicy와 RateDiscountPolicy 라는 구현체 클래스가 있다. 이 두 가지를 모두 스프링 빈으로 선언해보자.

// FixDiscountPolicy와 RateDiscountPolicy 라는 구현체에 각각 @Component를 넣어주고

// DiscountPolicy에 @Autowired를 넣어서 DI를 주입해보자.

NoUniqueBeanDefinitionException 가 발생!

 

이를 해결하기 위한 방법은 크게 3가지가 있다.

1. @Autowired 필드명 매칭

2. @Qualifier @Qualifier끼리 매칭 bean 이름 매칭

3. @Primary 사용


1.@Autowired 필드 명 매칭

 

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

@Autowired
private DiscountPolicy discountPolicy

↓ 필드 명을 빈 이름으로 변경 ↓

@Autowired
private DiscountPolicy rateDiscountPolicy

필드명이 rateDiscountPolicy로 정상 주입됨

 

2.@Qualifier

 

빈 이름을 변경하는 것이 아닌, 추가 구분자를 붙여주는 방법이다.

순서 : 빈 등록시 @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;
}

// 수정자 DI도 마찬가지로 가능

// 직접 빈을 등록할 때에도 @Qualifier를 사용할 수 있다.

 

3. @Primary 사용

 

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

@Component
@Primary //rateDiscountPolicy가 우선권을 가짐
public class RateDiscountPolicy implements DiscountPolicy {
...
}


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

@Primary를 사용함으로써, 원래 사용하던 생성자 주입 코드에서도 정상적으로 작동함을 알 수 있다.

 

그렇다면, 언제 @Primary를 사용하고, 언제 @Qualifier를 사용하는 것이 좋을까?

main DB connection을 획득하는 빈은 @Primary를, sub DB Connection을 획득하는 빈은 @Qualifier를 사용하자!

Annotation 만들어 사용하기

 

@Qualifier("mainDiscountPolicy") 와 같이 적으면 컴파일시 타입 체크가 안된다.

이 문제를 해결하기 위해 직접 애노테이션을 만들 수 있다.

annotataion pakage에 들어가서 직접 작성하자.→ mainDiscountPolicy라는 annotation을 만들자

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

→ 이렇게 annotation 패키지에 들어가서 본인이 원하는 이름으로 작성이 가능하다.

@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}

그러면 이와 같이 사용이 가능하다.

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

 


다음 게시글

조회한 빈이 모두 필요할 때 어떻게 해야할지 + 총 정리

728x90
반응형