본문으로 건너뛰기

필드 주입보다 생성자 주입을

인텔리J로 스프링부트를 사용하다보면
@Autowired(@Inject)가 있을 때, Field injection is not recommended. 라는 메세지를 볼 수 있다.
Field injection is not recommended. Spring Team recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies."
스프링 팀 에서는 빈을 주입할 때 항상 생성자 주입을 사용하는 것을 추천한다. 라고 한다.

다음 코드는 각각
생성자 주입 / 필드 주입 / 셋터 주입을 사용한 예 이다.

ConstructorInjection.java

@Component
public class ConstructorInjection {
private final MyServiceA myServiceA;
private final MyServiceB myServiceB;

ConstructorInjection(MyServiceA myServiceA, MyServiceB myServiceB) {
this.myServiceA = myServiceA;
this.myServiceB = myServiceB;
}
}

FieldInjection.java

@Component
public class FieldInjection {
@Autowired
private MyServiceA myServiceA;
@Autowired
private MyServiceB myServiceB;
}

SetterInjection.java

@Component
public class SetterInjection {
private MyServiceA myServiceA;
private MyServiceB myServiceB;

@Autowired
public void setMyServiceA(MyServiceA myServiceA) {
this.myServiceA = myServiceA;
}

@Autowired
public void setMyServiceB(MyServiceB myServiceB) {
this.myServiceB = myServiceB;
}
}

의존관계가 많아지면 코드가 어떻게 될까(생성자 주입일 경우)

HasManyDependencies.java

@Component
public class HasManyDependencies {
private final MyServiceA myServiceA;
private final MyServiceB myServiceB;
private final MyServiceC myServiceC;
private final MyServiceD myServiceD;
private final MyServiceE myServiceE;


HasManyDependencies(MyServiceA myServiceA,
MyServiceB myServiceB,
MyServiceC myServiceC,
MyServiceD myServiceD,
MyServiceE myServiceE, …… ) {
this.myServiceA = myServiceA;
this.myServiceB = myServiceB;
this.myServiceC = myServiceC;
this.myServiceD = myServiceD;
this.myServiceE = myServiceE;


}
}

필드주입 (@Autowired)의 간결함에서 많이 멀어지게 된다.
그럼에도 필드주입이 아닌 생성자 주입을 사용하는 이유?

  • 단일 책임의 원칙
    생성자 주입이 너무 번거롭다 = 해당 클래스가 많은 의존성을 띄고 있다.
    의존 관계가 많다 = 해당 클래스가 너무 많은 책임을 지고 있다.
    그럼으로 단일 책임원칙 에서 위반된다.
    생성자 주입을 사용하게되면 이 부분에 대해서 인식하기 쉽고 눈에 띄기 쉽다.

  • 의존 관계에 대해서 명시적으로 알게됨
    필수 의존 관계일 경우 생성자 주입을
    옵션 의존 관계일 경우에는 셋터 주입을 사용함으로써
    의존 관계가 필수인지 옵션인지 명확하게 구분이 됨.

  • 불변성
    생성자 주입을 사용할 경우 final 선언이 가능하다.
    final 을 사용함으로써 불변객체가 되고, 필요 의존 관계 주입만으로 불변성을 갖게 하는것이 가능하다

  • 순환의존성
    생성자 주입을 사용 할 경우 순환 의존에서 문제가 생긴다.
    A가 B를 주입. / B가 C를 주입 / C가 A를 주입 하는 경우에 BeanCurrentlyInCreationException이 발생한다.
    순환 의존을 할 경우에는 생성자 주입을 사용할 수 없다.
    그러나, 원래 순환의존은 안티패턴이기 때문에 클래스의 설계와 분리가 잘못되었을 가능성이 있으며
    그것을 알아차리게끔 해준다.

  • 재 이용성
    DI 컨테이너로 관리되는 클래스는 특정 DI 컨테이너에 의존하지 않고 POJO여야 한다.
    이렇게 하면 DI 컨테이너를 사용하지 않고 인스턴스화하여 단위 테스트가 가능해지고,
    또한 다른 DI 프레임워크로 전환할 수도 있다.
    그러나 필드 인젝션을 사용하는 경우, 해당 클래스에서 필요한 종속성을 인스턴스화하는 방법이 없다 (반영 제외).
    클래스를 인스턴스화하고 종속성 클래스를 사용하면 초기화되지 않았으므로 NullPointerException이 발생한다.
    즉, DI 컨테이너 이외에서는 재사용할 수 없게 된다. 생성자 주입을 사용하면 인스턴스화에 필요한 종속성을 강제할 수 있다.