프로필사진

Go, Vantage point

가까운 곳을 걷지 않고 서는 먼 곳을 갈 수 없다.


Github | https://github.com/overnew/

Blog | https://everenew.tistory.com/





티스토리 뷰

개발/Spring

[Spring] 9. 의존 관계 주입

EVEerNew 2022. 6. 29. 22:27
반응형

 

 

 

* 김영한님의 스프링 핵심원리 기본편 강좌를 수강하며 정리한 글입니다. *

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

 

 

이전 글에서는 컴포넌트와 스캔을 이용한 자동 빈 등록에 대해 알아보았다.

 

이번에는 컴포넌트를 이용해서 의존 관계를 주입하는 방법에 대해 알아보자.

 

생성자 주입

 

@AutoWired가 생성자에 적용되어 있다면, 빈에서 찾아 주입을 해준다.

생성자는 생성 시점에 단 한번 호출되므로 불변, 필수 의존 관계 주입을 위해 사용한다.

외부에서 임의 수정할 수 있는 방법을 막으므로써 불변을 보장해준다.

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

이는 자바의 final 키워드의 역할과 비슷하다.

실제로 final 키워드는 불변을 보장하고, 대신 값이 반드시 초기화 되어 있어야한다.

즉, 객체의 final 멤버 필드의 값은 생성 시점에 필수 요소이다.

따라서 final 키워드의 값은 생성자 주입을 해주는 것이 적절하다.

 

생성자는 단 하나만 있을 때 자동으로 AutoWired가 적용되지만, 여러 생성자가 있다면 반드시 AutoWired를 적용해주자.

 

생성자 주입의 경우 생성 과정에 AutoWired가 적용되어 있어서 생성과 주입이 동시에 일어난다.

만약 생성이 아직 안된 빈에 의존한다면 해당 빈을 먼저 생성한다.

 

수정자(Setter) 주입

 

특정 필드를 수정하는 setter함수와 동일 하다.

이 setter함수에 @AutoWired를 적용하면 된다.

 

private  MemberRepository memberRepository;
private DiscountPolicy discountPolicy;

@Autowired(required = false) //false 적용 시 생성 시점이 아닌 호출 시점에 주입됨
public void setMemberRepository(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
    this.discountPolicy = discountPolicy;
}

 

스프링은 빈을 생성하는 단계를 거치고 의존 관계를 주입한다.

따라서 일단 @Component가 붙은 클래스들을 빈으로 생성한 후, @AutoWired를 찾아서 이에 따라 빈을 찾아 의존관계를 주입한다.

그러므로 여전히 싱글톤이 지켜진다.

 

수정자 주입은 자바의 수정자와 동일하게 변경 가능성이 있는 경우 호출해 사용한다.

 

필드 주입

 

필드에 직접 @AutoWired 적용해 준다.

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

 

간결해 보이지만 private 멤버는 외부에서 변경이 안 된다.

getBean()을 통해 가져오는 스프링 테스트가 아니라,  순수한 자바 코드로 테스트를 위해 new 객체로 생성한다면 멤버에 값을 적용할 수 있는 방법이 없어 진다.

결국 수정자도 따로 필요하게 되므로 사용을 지양하자.

 

보통 간단한 스프링 테스트 코드에서만 사용한다.

 

 

일반 메서드 주입

 

init()과 같은 초기화 메서드(사실 아무 메서드나)에 @AutoWired를 적용하면 여러가지를 한번에 주입이 가능하다.

생성자나 수정자 주입을 사용하면 일반 메서드 주입을 사용할 일이 없다.

 

 

주입 옵션

 

의존 관계 주입 시점에 대상 빈이 컨테이너에 등록되지 않았다면 오류가 발생한다.

이때 사용하는 것이 옵션이다.

 

@AutoWired(required = false)

주입 시점에 대상 빈이 없다면 호출되지 않는다.

물론 생성자가 호출되지 않으면 안되므로 수정자 주입에 적용하자.

 

@Nullable 

주입 대상이 없다면 null이 주입된다.

 

Optional<대상 class>

주입 대상이 없다면 Optional.empty가 주입된다.

Optional은 NullPointException을 막기 위해 값이 null인지 검사해주는 Wapper class이다.

Optional<>의 코드

 

private MemberRepository memberRepository

@Autowired(required = false) //false 적용 시 생성 시점이 아닌 호출 시점에 주입됨
public void setMemberRepository1(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

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

@Autowired
public void setMemberRepository3(Optional<MemberRepository> memberRepository) {
    this.memberRepository = memberRepository;
}

 

@Nullable과 Optional<>은 호출을 막는 것이 아니므로 생성자 주입에도 사용이 가능하다.

 

 

생성자 주입이 항상 옳은 이유

 

최근의 스프링 프레임 워크들은 대부분 생성자 주입을 선택, 권장한다.

좋은 설계란 최대한 제약을 걸어 두는 것으로 실수로 변경되는 것을 방지한다.

또한 대부분의 어플리케이션은 의존 관계 주입시 변경할 일이 거의 없다.

따라서 생성자 주입으로 불변하게 설정하자.

 

만약 수정자로 의존 관계를 주입한다고 생각해보자.

스프링에서 테스트 한다면 생성 시점에 @AutoWired로 주입이 진행되어 필드 값에 빈이 주입될 것이다.

하지만 이를 순수 자바 코드로 테스트하면 생성 시점에는 생성자만 호출이 된다.

따라서 필드에 어떤 값도 주입되지 않을 수 있지만, 개발자는 이를 모르고 테스트를 진행하면 NullPointException이 발생한다.

만약 수정자 주입을 사용한다면 순수 자바에서는 동작하지 않아서, 프레임워크에 의존하게 된다.

 

생성자 주입은 순수 자바 코드에서든 스프링에서든 생성 시점에 호출되고,

순수 자바 테스트라면 디폴드 생성자를 없애서생성 시점에 미리 필요한 필드 값을 주입하도록 강제할 수 있다.

즉, 테스트에 훨씬 적합하다.

 

무엇보다 멤버 필드에 final 키워드가 적용 시, 생성자 호출 시점에만 값을 주입할 수 있다.

final 키워드는 반드시 초기화가 필요하므로 NullPointException을 막을 수 있다.

 

 

롬복 Lombok의 @RequiredArgsConstructor

 

물론 생성자 주입을 만들어 주는 것은 여간 귀찮은 일이 아니다.

이를 편리하게 만들어 주는 것이 롬복 라이브러리이다.

 

롬복 라이브러리를 사용하면 클래스에 어노테이션을 붙이는 것만으로도 자동으로 해당 메서드를 만들어준다.

예를 들어 Getter와 Setter가 필요하면 @Getter, @Setter을 적용하면 필드 변수를 get,set메서드로 접근할 수 있다. 

 

 

 

 

 

 

거기에다 @RequiredArgsConstructor를 붙이면 클래스의 final필드를 포함하는 생성자를 자동으로 만들어 준다.

 

 

 

 

 

 

 

이 라이브러리를 사용하면 필드를 추가, 제거할 때 굉장히 편리해진다.

거기에다 생성자가 하나면 자동으로 @AutoWired가 적용되므로 직접 생성자 주입 코드를 만들 필요가 없다.

 

 

중복 빈 조회 해결

 

@AutoWired을 이용하다 보면 부모 타입으로 조회된 여러개의 자식 타입이나 이름이 같아 발생하는 중복 조회 문제가 발생할 수 있다. 

(어떤 빈은 선택할지 몰라 발생하는 NoUniqueBeanDefinitionException 예외)

 

이를 해결하는 방법은 다음과 같다.

 

1. @AutoWired의 필드 이름 추가 매칭

 

@AutoWired는 만약 중복된 빈이 조회된다면, 그중에 필드 이름과 동일한 빈이 존재할때 그 빈을 매칭 시킨다.

 

예를 들어 DiscountPolicy 클래스를 상속하는 RateDiscountPolicy와 FixDiscountPolicy가 @Component가 적용되어 빈으로 등록되었다 하자.

DiscountPolicy로 @AutoWired를 진행하면 두 가지 빈이 조회된다.

하지만 만약 필드 이름이 두 가지 빈 중 하나와 매칭 된다면 해당 빈이 주입된다.

 

private final DiscountPolicy discountPolicy;

//rateDiscountPolicy로 매칭되는 매개변수 이름 설정
public OrderServiceImpl(DiscountPolicy rateDiscountPolicy) {
    this.discountPolicy = rateDiscountPolicy;
}

 

2. @Quilifier로 추가 구분자 제공

 

빈에 @Qualifier로 추가적으로 구별할 수 있는 정보를 제공해준다.

이는 생성자 뿐만아닌 수정자 주입에도 적용이 가능하다.

 

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

@Component
@Qualifier("mainDiscountPolicy")	//추가적인
public class RateDiscountPolicy implements DiscountPolicy{
...
}

//생성자 하나 뿐일땐 @AutoWired 생략 가능
public OrderServiceImpl(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
    this.discountPolicy = discountPolicy;
}

만약 해당 Qualifier를 찾지 못하면 해당 이름의 빈으로 조회한다.

 

단, @Qualifier("이름")은 직접 타자로 작성해야하고 IDE가 자동 완성이나 오류를 잡아주지 않는다.

따라서 오타로 인한 오류가 발생하기 쉬운데, 이 때 사용하는 것이 직접 만든 어노테이션을 한다.

 

실제 @Qualifier 어노테이션의 코드

@Qualifier와 동일한 기능을 하도록 위의 어노테이션들을 가져와서 다음과 같이 새로운 어노테이션을 만들 수 있다.

(스프링에서는 어노테이션을 조합해 사용하는 기능을 지원, 순수 자바는 x)

 

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

 

이제 이 어노테이션을  @Qualifier 대신 사용할 수 있다.

어노테이션을 선언하면 IDE에서도 자동완성이나 오류를 파악할 수 있다.

 

public OrderServiceImpl(@MainDiscountPolicy DiscountPolicy discountPolicy) {
    this.discountPolicy = discountPolicy;
}

 

 

 

3. @Primary 로 우선순위 적용

 

이름대로 적용 빈(컴포넌트)가 조회 우선권을 가진다.

따라서 @AutoWired 시 여러 빈이 조회 된다면 우선 순위를 가지는 빈을 주입시켜준다.

대부분 이 어노테이션을 사용해서 가장 빈도가 높은 빈(메인 데이터 베이스 커넥션)에 적용시켜 사용한다.

 

단, @Qualifier로 접근한 것이 @Primary보다 우선 권을 가지므로, 다른 빈의 주입을 원한다면 @Qualifier로 직접 지정해 사용하면 된다. (자동 < 수동)

 

 

Map과 List로 중복 빈 모두 저장하기

 

물론 해당 타입의 중복 조회된 빈들이 모두 필요할 때도 있다.

Map<string, 중복 타입>과 List<중복 타입> 자료구조를 이용하면 중복 조회된 빈들이 필드에 자동 주입된다.

 

 

 

 

이제 이 클래스에 접근하면 빈의 이름을 넘겨, 동적으로 원하는 것을 꺼내 쓸 수 있다.

 

 

의존 관계 자동 등록(@ComponentScan, @Component) VS 수동 등록(@Configuration, @Bean)

 

점차 자동 등록이 선호되는 추세로, 스프링 부트도 컴포넌트 스캔을 기본으로 사용한다.

이전 글  에서 구성 영역과 사용 영역의 분리를 위해 설정 정보인 AppConfig.class를 만들었다.

그런데 사실 자동 주입을 사용하면 구성 영역도 따로 작성할 필요가 없어진다.

수동 주입의 설정 정보는 점점 커질 수록 관리하는 빈이 많아서 부담이 될 수도 있다.

 

단, 어플리케이션의 기초가 되는 기술 지원 객체는 설정 정보에 수동 등록하여 한눈에 보기 쉽게(명확히) 만드는 것이 좋다.

 

 

 

 

 

반응형
댓글
반응형
인기글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함