[개발] - Spring/핵심 원리 구현
생성자 주입을 선택하라
완벽한 장면
2024. 2. 5. 17:22
생성자 주입을 선택해!!!
과거에는 수정자 주입과 필드 주입을 많이 사용했지만,
최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다. 그 이유는 다음과 같다.
불변
누락
프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우에
다음과 같이 수정자 의존관계인 경우
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository; // final 빼고
private DiscountPolicy discountPolicy;
// 이거 두 개 추가
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
//테스트 용도 추가
public MemberRepository getMemberRepository() {
return memberRepository;
}
// 생성자 추가
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
/*
discountPolicy 얘는 어떤 discountPolicy 구현체를 넣어줄 지 전혀 모른다.(클래스 정보x)
*/
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
OrderService 테스트
package inflearn.spring_core.order;
import inflearn.spring_core.config.AppConfig;
import inflearn.spring_core.member.Grade;
import inflearn.spring_core.member.MemberService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
// 강의 테스트를 위해 Impl 테스트 클래스를 하나 더 생성함
class OrderServiceImplTest {
private MemberService memberService; //수정
private OrderService orderService; //수정
// ------------------------ 중간 생략
// 테스트 수정 ------------------
@DisplayName("주문 생성 테스트")
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl(); // 이 괄호 안에 빨간줄이 나온다.
orderService.createOrder(1L, "itemA", 10000);
}
}
이제 이걸 막기 위해 final 활용
- 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다.
- 그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다. 다음 코드를 보자.
@Component
public class OrderServiceImpl implements OrderService {
// final 활용
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
//테스트 용도 추가
public MemberRepository getMemberRepository() {
return memberRepository;
}
// 요기
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
참고
: 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로,
필드에 final 키워드를 사용할 수 없다.
오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.
테스트 코드 - 요류 방지를 위해 아예 값을 다 넣어주고 실행시켰다.
class OrderServiceImplTest {
@DisplayName("주문 생성 테스트")
@Test
void createOrder() {
// 값 다 넣어줌
MemoryMemberRepository memberRepository = new MemoryMemberRepository();
memberRepository.save(new Member(1L, "name", Grade.VIP));
OrderServiceImpl orderService = new OrderServiceImpl(memberRepository, new FixDiscountPolicy());
Order order = orderService.createOrder(1L, "itemA", 10000);
assertThat(order.getDiscountPrice()).isEqualTo(1000); // 10퍼센트 할인 - 일단 이것만 확인
}
}
정리
728x90
반응형