오봉이와 함께하는 개발 블로그
Spring - 의존관계 자동 주입(옵션 처리, 생성자 주입을 선택하자) 본문
옵션 처리
스프링 빈을 옵셔널 하게 해서 등록을 안 해도 기본 로직으로 동작하게 하거나, 기본이 없으면 실행하지 않게 하는 등 주입할 스프링 빈이 없어도 동작해야 할 때가 있는데, @Autowired의 required라는 옵션은 기본값이 true로 되어 있어 자동 주입 대상이 없으면 오류가 발생한다.
- 자동 주입 대상을 옵션으로 처리하는 방법
- @Autowired(required = true) : 자동 주입할 대상이 없으면 수정자 메소드 자체가 호출 안됨
- org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 Null이 입력
- Optinal<> : 자동 주입할 대상이 없으면 Optional.empty가 입력된다.
테스트 코드
public class AutowiredTest {
@Test
void AutowiredOption() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
}
static class TestBean {
// 자동 주입할 대상 없으면 수정자 메소드 호출 안됨
@Autowired(required = false)
public void setNoBean1(Member noBean1) {
System.out.println("NoBean1 = " + noBean1);
}
// 자동 주입할 대상이 없으면 null 입력
@Autowired
public void setNoBean2(@Nullable Member noBean2){
System.out.println("noBean2 = " + noBean2);
}
// 자동 주입할 대상 없으면 Optional.empty 입력됨
@Autowired
public void setNoBean3(Optional<Member> noBean3) {
System.out.println("noBean3 = " + noBean3);
}
}
}
// 출력 결과
noBean2 = null
noBean3 = Optional.empty
- Member는 스프링 빈이 아니기 때문에 호출이 안됐다.
- setNoBean1()은 @Autowired(required=false)이기 때문에 호출 자체가 안됨
참고
@Nullable, Optional은 스프링 전반에 걸쳐서 지원된다. 생성자 자동 주입할 때 @Nullable을 넣어주면 파라미터가 n개 있을 때 특정 파라미터는 스프링 빈에 존재하지 않아도 생성자 호출을 하고싶다면 할 수는 있다.
생성자 주입을 선택하자
과거에는 수정자 주입, 필드 주입을 많이 사용했지만, 최근 DI 프렝미워크 대부분이 생성자 주입을 권장함
이유 1. 불변
- 대부분 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다.
- 오히려 애플리케이션 종료 전까지 변하면 안됨.
- 수정자 주입을 사용하면 setXXX메소드를 public으로 열어두어야 함.
- 누군가 실수로 변경할 수도, 변경하면 안되는 메소드를 열어두는 것은 좋은 설계 방법이 아님.
- 생성자 주입은 객체를 생성할 때 딱 한 번만 호출되므로 불변하게 설계할 수 있다.
이유 2. 누락
프레임워크 없이 순수한 자바 코드를 단위 테스트 할 때 수정자 의존관계인 경우 @Autowired가 프레임워크 안에서 동작할 때 의존관계가 없으면 오류가 발생하지만, 프레임워크 없이 순수한 자바 코드로 단위 테스트를 수행하면 오류가 발생한다.
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;
}
//...
}
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.createOrder(1L, "itemA", 10000);
}
해당 코드는 실행되지만 Null Pointer Exception이 발생된다.
-> memberRepository, discountPolicy 모두 의존관계 주입이 누락되었기 때문!
하지만 생성자 주입을 사용하면 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다. 또 IDE에서 어떤 값을 넣어줘야 하는지 알 수 있다.
// 생성자 주입 테스트 코드
public class OrderServiceImplTest {
@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);
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
이유 3. final 키워드
- 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다.
- final은 초기값을 넣어주는 것이 아닌 이상 생성자에서만 값을 세팅할 수 있다.
- 생성자에서 혹시 값이 설정되지 않는 오류를 컴파일 시점에 막아주는 장점이 있다.
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
}
//...
}
- discountPolicy 에 값을 설정해야 하는데, 이 부분이 누락되었다.
- 자바는 컴파일 시점에 오류를 발생시킨다.
- java: variable discountPolicy might not have been initialized
- 컴파일 오류는 세상에서 가장 빠르고 좋은 오류다.
- 자바는 컴파일 시점에 오류를 발생시킨다.
참고
수정자 주입을 포함 나머지 주입 방식은 모두 생성자 이후에 호출되기 때문에 필드에 final 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.
정리
프레임워크에 의존하지 않으며, 순수한 자바 언어의 특징을 잘 살리는 방법인 생성자 주입은 생성자 주입 방식을 선택하는 이유 중 하나이다.
기본적으로 생성자 주입을 사용하고, 필수 값이 아닌 경우 수정자 주입 방식을 옵션으로 부여하자. 생성자 주입과 수정자 주입은 동시에 사용할 수 있다!
항상 생성자 주입을 선택하자! 옵션이 필요할 때만 수정자 주입을 선택하고, 필드 주입은 금물이다.
출처 : 인프런 김영한 지식공유자님의 스프링 완전 정복 로드맵 강의
'BE > Spring' 카테고리의 다른 글
Spring - 의존관계 자동 주입(@Autowired 필드 명, @Qualifier, @Primary, 어노테이션 직접 만들기 (0) | 2022.06.10 |
---|---|
Spring - 의존관계 자동 주입(롬복과 최신 트랜드, 조회 빈이 2개 이상 - 문제) (0) | 2022.06.10 |
Spring - 의존관계 자동 주입(다양한 의존관계 주입 방법) (0) | 2022.06.09 |
Spring - 컴포넌트 스캔(필터, 중복 등록과 충돌) (0) | 2022.06.09 |
Spring - 컴포넌트 스캔(컴포넌트 스캔과 의존관계 자동 주입 시작하기, 탐색 위치와 기본 스캔 대상) (0) | 2022.06.08 |