본 프로젝트 자료는 김영한님의 스프링 핵심 원리 - 기본편 참고 제작됐음을 알립니다.
아래 코드 예시
public class A {
private B b;
public A() {
this.b = new B();
}
}
가장 큰 문제는 B 가 C 나 D, E 등으로 변경해야 할 때 A클래스의 생성자를 매번 변경해야하고 유연성이 떨어지는 결과를 가져준다. 그리고 처음에 배웠던 5원칙인 SOLID 중 DIP 원칙을 위반하는 꼴이기도 하다.
하지만 의존관계라는걸 활용하면 이러한 문제점들을 해결 할 수 있다.
어떤 객체가 다른 객체에 의존한다는 것은, 그 의존 대상의 변화에 취약하다는 뜻이다. DI를 이용하면 주입받는 대상이 바뀔지 몰라도 해당 객체의 구현 자체를 수정할 일은 없어진다.
기존 PizzaChef 클래스는 피자 레시피를 바꾸는 것이 쉽지 않았다. 생성자 코드 자체를 변경해주어야 했지만, DI를 이용하면 생성자의 인수만 다른 피자 레시피로 바꿔주면 된다.
DI를 이용한 객체는 자신이 의존하고 있는 인터페이스가 어떤 클래스로 구현되어 있는지 몰라도 된다. 따라서 테스트하기 더 쉬워진다.
다양한 의존관계 주입 방법이 존재하지만 크게 4가지 방법이 있다고 한다.
결론부터 말하자면 '생성자 주입'이 가장 좋다.
@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;
}
}
팁!) 이때 @Autowired 는 생성자가 1개만 존재하면 생략해도 문제 없다.
아래 코드 예시
@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 의 기본 동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.
필드에 @Autowired 어노테이션만 붙여주면 자동으로 의존성 주입 된다. 사용법이 매우 간단하기 때문에 가장 많이 접할 수 있는 방법이다.
아래 코드 예시
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
참고: 순수한 자바 테스트 코드에는 당연히 @Autowired가 동작하지 않는다. @SpringBootTest 처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.
참고: 다음 코드와 같이 @Bean 에서 파라미터에 의존관계는 자동 주입된다. 수동 등록시 자동 등록된 빈의 의존관계가 필요할 때 문제를 해결할 수 있다.
테스트를 활용한 코드 예시
@Bean
OrderService orderService(MemberRepository memberRepoisitory, DiscountPolicy discountPolicy) {
new OrderServiceImpl(memberRepository, discountPolicy)
}
일반 메서드 코드 예시
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
스프링 빈이 아닌 Member라는 클래스를 의존성 주입하는 경우의 코드 예시
@Autowired(required = false) // 호출 안됨
public void setNoBean1(Member noBean1) {
System.out.println("noBean1 = " + noBean1);
}
@Autowired
public void setNoBean2(@Nullable Member noBean2) {
System.out.println("noBean2 = " + noBean2);
}
@Autowired
public void setNoBean3(Optional<Member> noBean3) {
System.out.println("noBean3 = " + noBean3);
}
아래 코드는 결과값 출력 예시
setNoBean2 = null
setNoBean3 = Optional.empty
과거에는 수정자 주입과 필드 주입 등 다양한 주입으로 많이들 사용했지만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 공식적으로도 권장하고 있다.