의존성이란?
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-주입을-사용하자