[기본] 자동 등록의 의존성 주입

kiwonkim·2021년 7월 6일
0
post-thumbnail

이전 포스팅

스프링 컨테이너는 Config 파일로 생성하고, Config 파일은 @Bean을 이용한 수동등록과 @ComponentScan을 이용한 자동등록이 있다.

수동등록에서는 의존성주입을 명시적으로 해주는데 클래스에 @Component를 붙혀 등록하는 자동등록은 의존성 주입을 어떻게 수행할까?


자동등록의 의존성 주입

@Autowired

자동등록은 @Autowired 를 통해 스프링이 자동 주입해준다. 클래스에서 주입할 부분에 @Autowired를 추가하면 스프링빈에서 해당 타입을 찾아 의존성을 주입해준다.

자동등록의 의존성 주입은 @Component 내에서 주입할 부분에 @Autowired를 추가한다. 스프링은 빈 저장소에서 해당 타입을 찾아 주입한다.



여러 의존성 주입 방법

1. 생성자 주입

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;
    }
}
  • 생성자에 @Autowired
  • @Component 가 생성자가 1개인 경우 @Autowired 생략 가능
  • 필드에 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;
  }
}
//호출 안됨
@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에 @Autowired
  • 생성자 주입은 모든 필드가 반드시 주입되어야 하며, 스프링 빈에 존재하지 않으면 에러가 발생한다.
  • 선택, 변경 가능성이 있는 의존관계는 setter 주입으로 선택적 주입하도록 하자.

3. 필드 주입

@Component
public class OrderServiceImpl implements OrderService {
	
  @Autowired
  private MemberRepository memberRepository;
  
  @Autowired
  private DiscountPolicy discountPolicy;
  
}
  • 필드에 @Autowired 추가하는 방식
  • 외부에서 의존 관계 변경이 불가능해 거의 사용하지 않음.

스프링 빈 자동등록에서는 보통 생성자 주입을 사용한다. 생성자 주입은 필드에 final 추가가 가능해 의존 객체 누락을 컴파일 단게에서 막아주는 이점이 있다. 선택적으로 의존관계를 주입할 때는 setter 주입을 사용한다.



생성자 주입시 Lombok 활용

Lombok은 어노테이션을 기반으로 클래스의 생성자, getter, setter, ToString 등을 자동으로 만들어주는 라이브러리이다.

Lombok 라이브러리 및 설정 추가

`. Build.gradle 에 라이브러리와 설정 추가

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

//lombok 설정 추가 시작
configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}
//lombok 설정 추가 끝

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'

	//lombok 라이브러리 추가 시작
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'

	testCompileOnly 'org.projectlombok:lombok'
	testAnnotationProcessor 'org.projectlombok:lombok'
	//lombok 라이브러리 추가 끝

	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

2. Lombok 플러그인

인텔리제이 기준 File - Settings - Plugins - Lombok 추가

3. Annotation Processors 설정

어노테이션 기반인 Lombok 수행을 위해 File - Settings - Anootation Processors 에서 Enable annotation processing 체크.

Lombok 활용 빈 등록

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

memberRepository 와 discountPolicy 를 주입받는 OrderService를 스프링 빈으로 등록하는 기존 코드는 위와 같다.

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

기존 코드에 생성자가 1개이므로 @Autowired를 생략하고,
Lombok의 @RequiredArgsConstructor을 사용하면 필수인(final) 필드를 주입받는 생성자를 자동으로 만들어준다.
따라서 매우 간단한 코드만로 OrderService 를 스프링 빈으로 등록할 수 있게 된다.


Lombok은 어노테이션으로 생성자를 만들어준다. 그 중@RequiredArgsConstructor는 final 필드만 갖고 생성자를 만들어주며, 이를 활용해 매우 적은 코드로 의존성을 주입받으며 스프링 빈을 등록할 수 있다.



해당 타입 빈이 여러개일때 특정시키기

@Autowired는 필드/파라미터의 타입으로 빈을 검색하여 주입해준다. 동일 타입빈이 여러개이면 스프링은 에러를 발생시킨다. 이를 방지하기 위한 솔루션을 알아보자.

1. @Autowired 필드명 매칭

  • Autowired 는 먼저 타입으로 빈에서 탐색한다.
  • 동일 타입 빈이 여러개가 발견되면 필드명이나 파라미터명을 빈 이름으로 매칭을 시도한다.

2. @Qualifier 사용

  • Qualifier는 추가 구분자를 넣어주는 방식이다.
  • 빈 등록과 주입시 파라미터에 Qaulifier 이름을 추가하여 검색을 시도한다.
// 빈 등록시 Qualifier를 추가하고
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}

// 주입시 해당 파라미터에 @Qualifier를 추가한다. 동일 타입의 빈중 Qualifier 빈을 선택하게됨.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
	@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
	this.memberRepository = memberRepository;
	this.discountPolicy = discountPolicy;
}

3. @Primary 사용

  • @Primary는 동일 타입 빈이 매칭되면 해당 빈을 우선 주입하라는 뜻이다.
  • @Primary와 @Qualifier가 겹치면 @Qualifier가 우선순위를 가져간다.
// 빈 등록시 Primary를 추가하면된다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

의존성 주입시 동일 타입 빈이 존재하는 경우 솔루션
1. 필드명이나 파라미터명을 빈 이름으로 변경하는 것.
2. 빈과 파라미터에 @Qualifier를 추가하는 것.
3. 빈에 @Primary를 추가하는 것.
주로 활용되는 빈에 @Primary를 특이케이스의 경우 @Qualifier를 사용하자.



해당 타입 빈이 여러개일때 모두 주입하기

static class DiscountService {
//필드를 Map 또는 List로 선언
private final Map<String, DiscountPolicy> policyMap; // Map<String, 타입> 
private final List<DiscountPolicy> policies; // List<타입>

public DiscountService(Map<String, DiscountPolicy> policyMap,
	List<DiscountPolicy> policies) {
  this.policyMap = policyMap;
  this.policies = policies;
}

해당 타입의 스프링 빈이 모두 필요한 경우도 있다. 예를 들어 할인 서비스에서 클라이언트가 할인의 종류(rate, fix)를 선택할 수 있는 경우가 있다.
스프링은 이 경우 필드를 Map 이나 List로 선언하면 해당 타입의 모든 빈을 주입받는다.

Map은 빈이름:빈객체로 주입받게되고 List는 빈객체의 리스트로 주입받는다.


해당 타입의 모든 스프링 빈을 주입받고 싶다면 필드를 Map<String, 타입> 이나 List<타입> 으로 선언하고 주입받자.



본 글은 김영한님의 "스프링 핵심 원리 - 기본편" 강의내용 및 이해한 내용을 정리한 것입니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8

0개의 댓글