의존성 주입의 3가지 방법
객체를 직접 생성하는 것이 아니라 외부에서 주입 받아 사용하는 것을 의존성 주입(DI)라고 합니다. 스프링에서는 다양한 의존성 주입의 방법을 제공하는데 각각의 방법에 대해 알아보도록 하겠습니다.
1. 생성자 주입
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired //생성자가 1개만 있으면 생략해도 자동 주입(스프링 빈에만 해당)
public MemberService(MemberService memberService) {
this.memberService = memberService;
}
}
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
........
}
(Lombok 결합버전)
생성자를 통해 의존 관계를 주입 받는 방법으로 클래스에 생성자가 1개이고 주입 받을 객체가 빈에 등록되어 있다면 @Autowired를 생략할 수 있습니다.
2. 필드 주입
@Controller
public class MemberController {
@Autowired
private MemberService memberService;
}
이름 그대로 필드에 바로 주입하는 방법으로 필드에 @Autowired만 붙여주면 자동으로 의존성 주입이 됩니다.
단점
- 코드가 간결하지만 외부에서 변경이 불가능해 테스트가 힘들다.
- 프레임워크에 의존적이다.
3. 수정자 주입
@Controller
public class MemberController {
private MemberService memberService;
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}
}
필드의 값을 변경하는 수정자 메서드(Setter)를 통해 의존관계를 주입하는 방법입니다.
단점
- 세터 주입은 객체 생성 후에도 의존성을 변경할 수 있어 불변성을 보장하기 어렵다.
어떤 주입 방식을 사용하는 것이 좋을까?
최근에는 Spring을 포함한 DI 프레임 워크 대부분이 대부분 생성자 주입을 권장합니다.
지금부터 생성자 주입을 권장하는 이유에 대해 알아보도록 하겠습니다.
1. 불변성
대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 변경할 일이 없습니다. 생성자 주입은 객체 생성시 딱 1번만 호출되므로 이후에 호출되는 일이 없어 불변하게 설계할 수 있습니다.
또한 의존성 주입시 필드에 final 키워드를 사용할 수 있어 생성자에 혹시라도 값이 설정되지 않는 오류를 막을 수 있습니다.
나머지 주입 방식은 생성자 이후 호출되어 final 키워드 사용 불가
수정자 주입을 사용할 경우 setXxx 메소드를 public으로 열어둬야 하기 때문에 변경 가능성이 생김
그러므로 생성자 주입을 통해서 변경 가능성을 없애고 불변성을 보장하는 것이 좋습니다.
2. 테스트 용이
생성자 주입은 순수한 자바 코드로 단위코드를 작성하는 것이 용이합니다.
@Service
public class MemberService {
@Autowired
private MemberRepository memberRepository;
public void join(Long id) {
memberRepository.add(id);
}
}
필드 주입 방식에서 순수 자바 테스트 코드를 작성한다 생각했을 때
public class MemberServiceTest {
@Test
public void joinTest() {
MemberService memberService = new MemberService();
memberService.add(3);
}
}
해당 테스트는 Spring 위에서 동작이 되지 않아 의존관계 주입 누락으로 인해 Null Point Exception이 발생하게 됩니다. 이 문제를 해결 하기 위해서는 스프링을 추가적으로 사용하는 등 테스트 비용이 증가한다는 단점이 있습니다.
하지만 생성자 주입을 사용한다면 컴파일 시점에 객체를 주입 받아서 단위테스트를 진행할 수 있으며 컴파일 시점에 오류를 발견할 수 있다는 장점이 있습니다.
3. 순환참조 방지(2.6 이전 버전)
생성자 주입을 통해 구동 시점에서 순환 참조를 방지할 수 있습니다.
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
public void testA() {
serviceB.testB();
}
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
public void testB() {
serviceA.testA();
}
}
예를 들어 A가 B를 참조하고 B가 A를 참조하는 상황이라고 가정했을 때 다른 방식의 주입들은 빈이 생성된 후 참조를 하기 때문에 애플리케이션이 오류없이 실행되게 될 것이고 실제 호출 시점까지 문제를 알 수 없게 됩니다. 그 결과 두 메소드는 서로를 계속해서 호출하게 되고 서버가 죽게 될 것입니다.
하지만 생성자 주입을 이용하면 구동 시점에 객체의 생성과 조립이 동시에 일어나기 때문에 미리 문제를 파악할 수 있습니다.
스프링 부트 2.6 부터는 순환 참조가 기본적으로 허용하지 않아서 필드 주입 방식에서도 미리 문제를 파악할 수 있다. 가장 베스트는...애초에 순환 참조가 잃어나지 않도록 잘 설계하는 것이 중요할 것 같습니다.
정리
생성자 주입은 불변성, 테스트 용이, 순환 참조를 방지할 수 있어 권장된다. 필드 주입과 수정자 주입은 각각의 장점이 있지만 불변성 보장과 테스트 용이성 측면의 단점이 커서 생성자 주입이 주로 이용된다.
'Backend > spring' 카테고리의 다른 글
[Spring] OpenFeign란? (1) | 2024.09.11 |
---|---|
[Spring]스프링 이벤트를 이용한 도메인 의존성 분리 및 고려할 점 (2) | 2024.09.09 |
[Spring] 서블릿 필터(Filter), 인터셉터(Interceptor) 개념과 차이점 (0) | 2024.08.05 |
[Spring] Dispatcher Servlet이란?? (1) | 2024.06.08 |
[Spring] Rest Docs으로 API 문서화하기 (1) | 2023.06.30 |