(인프런) 김영한님의 스프링 핵심 원리-기본편을 공부하고 리뷰한 글입니다.
2. 옵션 처리
@Autowired를 사용했지만 주입할 스프링 빈이 없어도 동작해야할 때가 있다.
자동 주입할 대상이 없을 때, @Autowired만 사용하면 오류가 발생한다. (기본적으로 required=true이기 떄문에)
package hello.core.autowired;
public class AutowiredTest {
@Test
void AutoWiredOption() {
// TestBean 스프링 빈 등록
ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
}
static class TestBean {
// 자동으로 주입할 대상이 없으면 오류 발생
@Autowired // required=true
public void setNoBean1(Member noBean1) {
// Member 는 스프링 빈이 아님
System.out.println("noBean1 = " + noBean1);
}
}
1. 자동 주입 대상을 옵션으로 처리하는 방법
자동 주입할 대상이 없을 때도 로직을 실행시키고 싶을 때 사용하는 방법이다.
1) @Autowired(required=false) : 자동 주입할 대상이 없으면, 수정자 메서드 자체가 호출X
// 1. 자동으로 주입할 대상이 없으면, 수정자 메서드 호출X
@Autowired(required = false)
public void setNoBean1(Member noBean1) {
// Member 는 스프링 빈이 아님
System.out.println("noBean1 = " + noBean1);
}
2) org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면, null이 입력됨
// 2. 자동 주입할 대상이 없으면, null 호출
@Autowired
public void setNoBean2(@Nullable Member noBean2) {
System.out.println("noBean2 = " + noBean2);
}
3) Optional<> : 자동 주입할 대상이 없으면, Optional.empty 가 입력됨
// 3. 자동 주입할 대상이 없으면, Optional.empty 호출
@Autowired
public void setNoBean3(Optional<Member> noBean3) {
System.out.println("noBean3 = " + noBean3);
}
2. AutowiredTest 실행 결과
Member는 스프링 빈이 아니므로, 자동 주입할 대상이 없다.
(참고) @Nullable, Optional은 스프링 전반에 걸쳐서 지원된다. 예를 들어, 생성자 주입에서 특정 필드에만 사용해도 된다.
3. 생성자 주입을 선택해라!
과거에는 수정자 주입, 필드 주입을 많이 사용했지만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다. 생성자 주입을 사용해야하는 이유를 아래에서 차례로 설명한다.
1. 불변
생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 불변하게 설계할 수 있다.
- 대부분 애플리케이션 종료시점까지 의존관계를 변경할 일이 없고 변경해서도 안된다.
- 수정자 주입을 사용하면, public인 setter 메서드를 통해 의존관계를 변경할 수 있는 문제가 있다.
2. 누락
프레임워크 없이 순수한 자바 코드를 단위 테스트할 때, OrderServiceImpl이 수정자 주입을 하는 경우와 생성자 주입을 하는 경우를 비교해보자.
1) OrderServiceImpl 수정자 주입을 한 경우
<OrderServiceImpl 코드 수정>
OrderServiceImpl의 의존관계 주입을 수정자 주입방식으로 하였다.
@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;
}
}
<AppConfig의 orderServcie() 코드 수정>
OrderServiceImpl의 기존 코드는 주석처리된 부분처럼 생성자 주입이었는데, 수정자 주입으로 바뀌면서 AppConfig의 orderService()에서 생성자를 사용하는 부분이 컴파일 오류가 난다. 아래처럼 임시로 주석처리를 해두고 null을 반환하는 코드를 바꾼다. (테스트 후에는 다시 원상복귀 해야함) 해당 부분에 대한 이해가 필요하다면 여기서
<OrderServiceImplTest 코드 작성>
class OrderServiceImplTest {
// 순수 자바로 OrderServiceImpl 테스트
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
Order order = orderService.createOrder(1L, "itemA", 10000);
}
}
NPE(NullPointerException) 예외가 발생한다. memberRepository, discountPolicy 모두 의존관계 주입이 누락되었기 때문이다.
<OrderServiceImpl Test 실행 결과>
2) OrderServiceImpl 생성자 주입을 한 경우
<OrderServiceImpl 코드 수정>
OrderServiceImpl의 의존관계 주입을 생성자 주입방식으로 하였다.
@Component
public class OrderServiceImpl implements OrderService{
// 생성자 주입을 쓰면 final 키워드를 사용해 생성자에서 초기화 누락을 방지, 불변하도록 설계O
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// 생성자 주입
@Autowired // 생성자가 1개일 때 @Autowired 생략O
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
<OrderServiceImplTest 코드 컴파일 오류>
class OrderServiceImplTest {
// 순수 자바로 OrderServiceImpl 테스트
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
Order order = orderService.createOrder(1L, "itemA", 10000);
}
}
생성자 주입을 사용하면 주입할 데이터를 누락했을 때, 컴파일 오류가 발생한다. IDE에서 바로 어떤 값을 필수로 주입해야 하는지 알 수 있다.
3) OrderServiceImpl 생성자 주입을 한 경우, 컴파일 오류 부분 해결
컴파일 오류를 해결하기 위해 직접 MemoryMemberRepository, Member, FixDiscountPolicy 인스턴스를 생성해서 주입해주었다.
class OrderServiceImplTest {
// 순수 자바로 OrderServiceImpl 테스트
@Test
void createOrder() {
// 자바코드로 테스트를 하기 위해 MemoryMemberRepository, Member, FixDiscountPolicy 인스턴스 생성
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);
// 생성한 주문의 할인 금액이 1000원이 맞는지 검증
assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
Q. 위 코드에서 OrderServiceImpl 객체 인스턴스를 생성할 때 위에서 생성한 MemoryMemberRepository 객체가 아닌, new로 새로 생성한 MemoryReposiotory를 주입해도 문제가 되지 않는 이유는?
여기서 생성한 MemoryMemberRepository 2개는 다른 객체가 맞다. 하지만, MemoryMemberRepository 내의 store는 static 변수이므로 인스턴스들이 공유하게 된다. 2 객체 모두 동일한 store 변수를 바라보고 있기 때문에 새롭게 만든 MemoryMemberRepository로 OrderServiceImpl 객체를 생성해도 NPE가 발생하지 않는 것이다.
[참고] https://www.inflearn.com/questions/474564
3. final 키워드
생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 그래서 생성자에서 값이 초기화되지 않는 오류를 컴파일 시점에 막아준다.
@Component
public class OrderServiceImpl implements OrderService{
// 생성자 주입을 쓰면 final 키워드를 사용해 생성자에서 초기화 누락을 방지, 불변하도록 설계O
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// 생성자 주입
@Autowired // 생성자가 1개일 때 @Autowired 생략O
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
//this.discountPolicy = discountPolicy;
}
...
}
만약 discountPolicy .필드 값을 초기화하지 않으면, 아래와 같이 컴파일 오류가 발생한다.
컴파일 오류는 세상에서 가장 빠르고, 좋은 오류다!!
(참고) 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.
<정리>
- 생성자 주입 방식은 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이다.
- 기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여한다. 생성자 주입과 수정자 주입을 동시에 사용할 수 있다. 필드 주입은 사용하지 않는게 좋다.
Q. 필드 주입을 권장하지 않고, 생성자 주입을 권장하는 이유는?
1) 필드 주입은 SpringContainer를 거치지 않고 주입할 방법이 없다. 즉, SpringContainer 없이는 테스트 코드를 작성할 수 없다는 문제점이 있다.
2) 생성자 주입은 테스트할 때 SpringContrainer로부터 주입을 받거나 SpringContainer 없이 임의로 개발자가 생성하여 주입할 수도 있다.
[참고] https://www.inflearn.com/questions/513214
'Spring > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
[스프링 핵심 원리] 07. 의존관계 자동 주입 - 조회 빈이 2개 이상(문제) & @Autowired 필드 명, @Qualifier, @Primary & 애노테이션 직접 만들기 (0) | 2022.05.19 |
---|---|
[스프링 핵심 원리] 07. 의존관계 자동 주입 - 롬복과 최신 트랜드 (0) | 2022.05.18 |
[스프링 핵심 원리] 07. 의존관계 자동 주입 - 다양한 의존관계 주입 방법 (0) | 2022.05.18 |
[스프링 핵심 원리] 06. 컴포넌트 스캔 - 중복 등록과 충돌 (0) | 2022.05.18 |
[스프링 핵심 원리] 06. 컴포넌트 스캔 - 탐색 위치와 기본 스캔 대상 & 필터 (0) | 2022.05.18 |