의존성이란?
class HelloWorld {
private SayHello sayHello;
public HelloWorld() {
this.sayHello = new SayHello();
}
public startHelloWorld() {
this.sayHello.hello();
}
}
HelloWorld 클래스에서 hello 함수가 호출되기 위해서는 SayHello 클래스가 필요함
이 때 HelloWorld 클래스는 SayHello 클래스의 의존성을 가진다고 이야기 함
의존성 주입의 종류
필드 주입 (Field Injection)
@Component
public class Juice {
@Autowired
private Orange orange;
}
세터 주입 (Setter Injection)
@Component
public class Milk {
private Cereal cereal;
@Autowired
public void setCereal(Cereal cereal) {
this.cereal = cereal;
}
}
생성자 주입 (Constructor Injection)
public class ExampleCase {
private final ChocolateService chocolateService;
private final DrinkService drinkService;
@Autowired
public ExampleCase(ChocolateService chocolateService, DrinkService drinkService) {
this.chocolateService = chocolateService;
this.drinkService = drinkService;
}
}
@Autowired 어노테이션을 생략 할 수 있다.@Autowired 어노테이션을 붙여주어야 한다. (여러개의 생성자에 @Autowired를 붙일 수는 없음)필드 주입 대신 생성자 주입을 사용하는 이유
<aside>
💡 @Autowired를 활용한 의존성 주입을 필드 주입이라고 합니다.
필드 주입은 사용법이 매우 간단해서 대부분 의존성 주입을 필드 주입으로 접하지만,
편리하단 것 말고는 장점이 없어서 스프링 4.3부터는 사용하지 않는 것을 권장한다고 합니다.
</aside>
단일 책임 원칙 관점
field injection으로 의존성을 추가하기에 굉장히 간단하다. (클래스에 @Autowired를 선언하고, 필드만 추가하면 끝이다!) 하지만, 의존하는 객체가 많다는 것은, 하나의 클래스가 많은 책임을 가진다는 의미이다.
상대적으로 Constructor injection을 사용할 때에는 Constructor 매개변수가 많아지면서 잘못되어간다는 느낌을 받기 쉽다. 즉, 리팩토링을 필요로 한다는 좋은 지표가 될 수 있다.
필드에 final 키워드 사용
Constructor Injection과 다르게 Field Injection은 final을 선언할 수 없습니다. 그래서 객체가 변할 수 있습니다.
순환 참조 방지
순환 참조는 A -> B를 참조하면서, B -> A를 참조하는 경우 발생하는 문제이다.
Constructor Injection을 사용하면, 아래와 같이 어플리케이션 실행 시점에 순환 참조를 확인할 수 있다.
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| a defined in file [...\\injection\\constructor\\A.class]
↑ ↓
| b defined in file [...\\injection\\constructor\\B.class]
└─────┘
• field injection, setter injection에서는 메서드 실행 시점에만 발견할 수 있다.
DI 컨테이너의 결합성과 테스트 용이성
DI 프레임워크의 핵심 아이디어는 관리되는 클래스가 DI 컨테이너에 의존성이 없어야합니다. 즉, 필요한 의존성을 전달하면 독립적으로 인스턴스화 할 수 있는 단순 POJO여야합니다. DI 컨테이너 없이도 유닛테스트에서 인스턴스화 시킬 수 있고, 각각 나누어서 테스트도 할 수 있습니다. 컨테이너의 결합성이 없다면 관리하거나 관리하지 않는 클래스를 사용할 수 있고, 심지어 다른 DI 컨테이너로 전환할 수 있습니다. 하지만, Field Injection을 사용하면 필요한 의존성을 가진 클래스를 곧바로 인스턴스화 시킬 수 없습니다.
Feild Injection 을 통한 의존성 주입 객체를 사용한 테스트코드
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SpringInjectionApplication.class)
class ConstructorInjectionTest {
@Autowired
private Water water;
@Test
void constructor_injection() {
assertThat(water).isNotNull();
assertThat(water.getTeaBag()).isNotNull();
assertThat(water.getTeaBag()).isInstanceOf(Chamomile.class);
}
}
Constructor Injection 을 통한 의존성 주입 객체를 사용한 테스트코드
class ConstructorInjectionTest {
@Test
@DisplayName("생성자 주입 사용 시, 스프링 컨테이너에 의존하지 않고 테스트할 수 있다.")
void constructor_injection_without_dependence() {
Water water = new Water(new GreenTea());
assertThat(water).isNotNull();
assertThat(water.getTeaBag()).isNotNull();
assertThat(water.getTeaBag()).isInstanceOf(GreenTea.class);
}
}
NullPointerException 방지
field injection, setter injection은 new 키워드를 사용하여 객체를 생성한다면, 필수 협력자가 셋팅되지 않은 객체를 사용하려고 할때, NullPointerException이 발생한다.
하지만, constructor injection을 사용한다면, 필수 협력자가 셋팅되지 않은 상태에서는 생성조차 하지 못하게 된다.(필드 셋팅을 강제한다.) 따라서, NullPointerException은 발생하지 않을 것이다.
https://velog.io/@aidenshin/필드주입-생성자-주입방식으로-변경 https://upcake.tistory.com/417 https://velog.io/@dahye4321/Constructor-주입을-사용하자