셰프
가 요리를 하려면 B 클래스 식재료
가 필요합니다.
즉, 셰프
는 식재료
에 의존합니다.@Component
, @Service
, @Repository
등의 어노테이션을 클래스에 붙여, "이 클래스로 만든 객체는 Spring 컨테이너가 관리해 주세요"라고 알려줍니다.
이렇게 컨테이너가 관리하는 객체를 @Service
public class MemberService {
// MemberService가 MemberRepository를 직접 생성합니다. (강한 결합)
private final MemberRepository memberRepository = new MemberRepository();
public void join(Member member) {
memberRepository.save(member);
}
}
// 1. MemberRepository를 Bean으로 등록합니다.
@Repository
public class MemberRepository {
// ... 데이터 저장 로직 ...
}
// 2. MemberService가 생성될 때, 생성자를 통해 MemberRepository를 주입받습니다.
@Service
public class MemberService {
private final MemberRepository memberRepository;
// Spring 컨테이너가 이 생성자를 보고, 자신이 관리하는 MemberRepository Bean을 자동으로 넣어줍니다.
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
// ...
}
의존성 주입을 사용하는 가장 큰 이유는느슨한 결합(Loose Coupling)을 통해 코드의 유연성과 재사용성을 높이기 위함입니다. 객체가 의존성을 직접 생성하면, 그 의존성이 변경될 때마다 해당 객체의 코드를 직접 수정해야 합니다. 하지만 DI를 통해 외부에서 의존성을 주입받으면, 다른 구현체로 손쉽게 교체할 수 있어 변화에 유연하게 대처할 수 있습니다. 또한, 실제 객체 대신 가짜(Mock) 객체를 주입하기 쉬워져단위 테스트(Unit Test)가 매우 용이해집니다.
Spring에는 생성자 주입, 세터(Setter) 주입, 필드(Field) 주입 방식이 있습니다. 이 중생성자 주입이 가장 권장되는 이유는 다음과 같습니다. 첫째, 생성 시점에 모든 의존성이 주입되므로객체의 불변성(Immutability)을 확보할 수 있습니다. 둘째, 의존성을final
키워드로 선언할 수 있어, 런타임에 의존성이 변경될 위험을 막을 수 있습니다. 셋째, 생성자의 인자가 많아진다는 것은 해당 객체가 너무 많은 책임을 지고 있다는 신호이므로,객체의 책임(SRP)을 다시 한번 생각해 볼 기회를 줍니다. 마지막으로, 순환 참조 문제를 컴파일 시점에 발견할 수 있습니다.