본문 바로가기

Spring

Spring 에서 생성자를 통해 의존관계를 주입하는 이유

Spring 에서 DI 컨테이너에 의해 의존관계가 주입될 때 선택할 수 있는 방법은 3가지가 있다. 필드 객체 선언을 통한 주입, setter 메서드를 통한 의존관계 주입, 생성자를 통한 의존관계 주입이다. 이중에서 왜 항상 생성자를 통해서 의존관계를 주입하는 방식으로 코드를 작성해 왔는지 이유를 알아보려고 한다

1. final keyword 사용 가능

필드 객체나 setter 를 통해서는 final keyword 를 사용할 수 없다. 그 이유는 final 로 선언된 필드 객체는 초기화가 되어야 하는데 초기화가 이루어지는 방식이 크게 3가지이다. 

1. 선언과 동시에 초기화

2. 인스턴스 블록

3. 생성자

따라서 위 3가지 방법이 아닌 이상 final 로 선언된 필드 객체가 초기화될 수 없으므로 필드 객체나 setter 를 통해서는 final 을 사용할 수 없게 된다.

final 로 선언했을 때의 장점으로는 주입받는 객체의 reference 값을 재할당하지 못하도록 고정할 수 있다는 장점이 있다. (객체의 불변성을 보장하는 것은 아니다) 대게 런타임 시에 의존 관계 변경이 필요한 일은 없기 때문에 변경의 가능성을 최소화하는 것이 좋다. 

 

2. Unit Test 코드 작성

위 서비스 코드에서 doSomething 메서드에 대한 단위 테스트를 작성하고자 할 때 PracticeService 의 의존관계인 PracticeRepository 를 접근할 수 있는 방법이 없다. 따라서 NPE 가 발생하게 되어서 단위 테스트를 수행할 수 없게 된다. 

이를 해결하기 위해 @SpringBootTest 를 통해 모든 빈들을 가져와서 테스트하는 경우 테스트 비용이 증가되며 이는 단위 테스트가 아니게 된다. 

하지만 생성자를 통해 의존관계를 주입한다면 다음과 같이 단위 테스트를 작성할 수 있게 된다.

 

3. 순환 참조 방지 가능

생성자 주입을 활용하면 애플리케이션 구동 시점(객체의 생성시점)에 순환 참조 에러를 예방할 수 있다. 다음과 같이 필드 주입을 통해 서로 호출하는 코드가 있다고 하자.

위와 같이 순환 참조가 되어 있는 경우 memberService 에서 save 메서드가 호출되면 userService 에서 다시 memberService 의 save 메서드가 호출된다. 결국 StackOverflow 에러가 발생하게 된다. 하지만 생성자를 통해 의존관계를 주입한다면 애플리케이션 구동 시점(객체의 생성시점)에 에러가 발생하게 된다. Bean 에 등록하기 위해 생성자 호출시에 순환 참조가 발생하는 것을 알 수 있기 때문이다.

new MemberService(new UserService(new MemberService(...)))

※ 스프린 2.6부터는 순환 참조가 기본적으로 허용되지 않는다고 한다.