스프링 컨테이너는 Config 파일로 생성하고, Config 파일은 @Bean을 이용한 수동등록과 @ComponentScan을 이용한 자동등록이 있다.
수동등록에서는 의존성주입을 명시적으로 해주는데 클래스에 @Component를 붙혀 등록하는 자동등록은 의존성 주입을 어떻게 수행할까?
자동등록은 @Autowired 를 통해 스프링이 자동 주입해준다. 클래스에서 주입할 부분에 @Autowired를 추가하면 스프링빈에서 해당 타입을 찾아 의존성을 주입해준다.
자동등록의 의존성 주입은 @Component 내에서 주입할 부분에 @Autowired를 추가한다. 스프링은 빈 저장소에서 해당 타입을 찾아 주입한다.
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;
}
}
@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);
}
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
스프링 빈 자동등록에서는 보통 생성자 주입을 사용한다. 생성자 주입은 필드에 final 추가가 가능해 의존 객체 누락을 컴파일 단게에서 막아주는 이점이 있다. 선택적으로 의존관계를 주입할 때는 setter 주입을 사용한다.
Lombok은 어노테이션을 기반으로 클래스의 생성자, getter, setter, ToString 등을 자동으로 만들어주는 라이브러리이다.
`. 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 체크.
@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는 필드/파라미터의 타입으로 빈을 검색하여 주입해준다. 동일 타입빈이 여러개이면 스프링은 에러를 발생시킨다. 이를 방지하기 위한 솔루션을 알아보자.
// 빈 등록시 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;
}
// 빈 등록시 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<타입> 으로 선언하고 주입받자.
본 글은 김영한님의 "스프링 핵심 원리 - 기본편" 강의내용 및 이해한 내용을 정리한 것입니다.