의존성 주입 3가지 방법을 알아보고 어디에 사용해야할지 알아봅시다.
의존성 주입이란 의존하는 객체를 외부에서 생성 후 넣어주는것을 의미합니다.
만약 스파게티를 만든다고 해볼께요
사실 맛을 잘 못느껴서 그런지 면에 어떤 소스를 넣는지에 따라 달라지더라구요
토마토 소스와 너무 강결합 되어 있기 때문에 토마토 스파게티만 먹게 될거에요.
// Spaghetti.java
public class Spaghetti {
// 요리에 사용할 소스
private Source source;
// 생성자
public Spaghetti() {
// 나는 토마토 소스만 가질 수 있기 때문에 토마토 스파게티만 만들 수 있어
// 토마토 소스와 너무 강하게 결합되어 있어요.
this.source = new TomatoSource();
}
public void addSource() {
source.add();
}
}
// Source.interface
public interface Source {
void addSource();
}
// 토마토소스는 인터페이스 소스를 구현한 구현체
public class TomatoSource implements Source{
@Override
public void addSource() {
System.out.println("토마토 소스를 넣습니다!!!")
}
}
만약 다음과 같이 외부에서 소스를 미리 만들어서 스파게티가 만들어질 때 넣어준다면, 소스에 따라서 다양한 스파게티를 만들 수 있습니다.
// Spaghetti.java
class Spaghetti {
// 요리에 사용할 소스
private final Source source;
// 생성자
public Spaghetti(Source source) {
// 외부에서 이미 만들어진 소스를 받습니다.
// 오우!, 토마토 이외에 크림도 받을 수 있고 올리브도 받을 수 있어요!
this.source = source;
}
public void addSource() {
source.addSource();
}
}
의존성 주입 방법에는 4가지가 있습니다.
다만, 스프링 공식 문서에서는 다음 2가지를 권장합니다. 1. 생성자 주입, 2. setter 주입
간략하게 설명하면 다음과 같습니다.
의존관계가 변경되지 않는 경우 - 생성자 주입
선택적이거나 변경가능한 경우 - setter 주입
생성자를 통해서 의존 관계를 주입 받는 방법
생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
딱 1번만 호출되는 특징을 통해 불변 및 필수 의존관계에 사용됩니다.
사실, 서비스를 개발하면서 대부분의 의존관계는 불변입니다. 이를 위해 생성자 주입을 통해 불변관계임을 명시하는 것이 좋습니다.
// Spaghetti.java
@Component
class Spaghetti {
// 요리에 사용할 소스, final 키워드 사용
private final Source source;
// 생성자
public Spaghetti(Source source) {
// 외부에서 이미 만들어진 소스를 받습니다.
this.source = source;
}
}
setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계 주입
선택, 변경 가능성이 있는 의존관계에 사용
만약 의존관계를 필수로 넣지 않는다면, 필수가 아님을 명시해줍니다.@Autowired(required=false)
setter는 public으로 열려있기 때문에 언제든 변경이 가능합니다. setter 주입이 의도한 대로 가변 의존관계에 명확하게 사용하는것이 필요합니다.
// Spaghetti.java
@Component
public class Spaghetti {
private Source source;
@Autowired
public void setSource(Source source) {
this.source = source;
}
}
클래스 멤버 필드에 바로 주입하는 방법
코드가 간결해진다는 특징이 있음
DI 컨테이너가 없으면 아무것도 할 수 없다.
intelliJ 에서 필드 주입을 사용하면 바로 노란색 밑줄이 그어지며 Field injection is not recommended
라는 경고가 발생합니다.
필드 주입을 사용하지 않는 이유는 다음과 같습니다.
사실 필드주입을 사용하지 말아야하는 이유보다는, 불변/가변 이라는 상황에 따라 생성자 주입, setter 주입을 적절하게 사용해야한다는 의미가 더 큽니다.
불명확
field 주입을 하는 필드는 final 키워드를 통해 불변으로 만들 수 없습니다. 그렇다고 해서 setter로 가변적이라는 의미도 아닙니다. 그냥 이도저도 없이 애매합니다.
DI 컨테이너와 강결합
필드주입을 사용하게 된다면 DI 컨테이너 안에서만 작동합니다. 순수 자바 코드로 테스트하기 어렵습니다.
만약 생성자 방식으로 의존성을 주입했다면, 테스트코드에서 DI컨테이너에 등록되지 않은 일반 자바 코드로 테스트가 가능합니다.
class InjectionApplicationTests {
@Test
void testSource() {
Spaghetti spaghetti = new Spaghetti(new TomatoSource());
spaghetti.addSource();
}
}
이러한 이유로 필드 인젝션은 다음과 같은 제한된 상황에서만 사용됩니다.
// Spaghetti.java
@Component
public class Spaghetti {
@Autowired
private Source source;
public void addSource() {
this.source.addSource();
}
}
일반 메서드를 통해서 의존관계 주입
@Autowired
어노테이션은 어느 메서드에도 넣을 수 있음
한번에 여러 필드를 주입 받을 수 있습니다.
다만, 일반적으로 잘 사용하지 않습니다.
모호합니다. final 키워드로 불변을 만들수 없고, 한번에 여러 필드받을것이라면 차라리 생성자 주입을 사용하는것이 좋습니다.
@Component
public class Spaghetti {
private Source source;
@Autowired
public void init(Source source) {
this.source = source;
}
}