우리가 지금까지 써왔던 Field Injection Type. 왜 바꿔야 할까?
스프링에서의 소개
– 번역
http://opennote46.tistory.com/216
Injection 방법 세가지 타입을 예제를 들어 Bad, Ugly, Good으로 평가
https://kinbiko.com/java/dependency-injection-patterns/
http://olivergierke.de/2013/11/why-field-injection-is-evil/
Field Injection 변경하는 것을 고려해야 하는 좀 더 자세한 내용
https://www.vojtechruzicka.com/field-dependency-injection-considered-harmful/
– 번역
http://www.mimul.com/pebble/default/2018/03/30/1522386129211.html
Injection 방법
Constructor
private DependencyA dependencyA; private DependencyB dependencyB; private DependencyC dependencyC; @Autowired public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) { this.dependencyA = dependencyA; this.dependencyB = dependencyB; this.dependencyC = dependencyC; }
Setter
private DependencyA dependencyA; private DependencyB dependencyB; private DependencyC dependencyC; @Autowired public void setDependencyA(DependencyA dependencyA) { this.dependencyA = dependencyA; } @Autowired public void setDependencyB(DependencyB dependencyB) { this.dependencyB = dependencyB; } @Autowired public void setDependencyC(DependencyC dependencyC) { this.dependencyC = dependencyC; }
Field
@Autowired private DependencyA dependencyA; @Autowired private DependencyB dependencyB; @Autowired private DependencyC dependencyC;
왜 Constructor Injection을 권장하나?
- 단일 책임의 원칙
생성자의 인자가 많을 경우 코드량도 많아지고, 의존관계도 많아져 단일 책임의 원칙에 위배된다.
그래서 Constructor Injection을 사용함으로써 의존관계, 복잡성을 쉽게 알 수 있어 리팩토링의 단초를 제공하게 된다. - 테스트 용이성
DI 컨테이너에서 관리되는 클래스는 특정 DI 컨테이너에 의존하지 않고 POJO여야 한다.
DI 컨테이너를 사용하지 않고도 인스터화 할 수 있고, 단위 테스트도 가능하며 다른 DI 프레임워크로 전환할 수도 있게 된다. - Immutability
Constructor Injection에서는 필드는 final로 선언할 수 있다.
불변 객체가 가능한데 비해 Field Injection은 final을 선언할 수 없기 때문에 객체가 변경 가능한 상태가 된다. - 순환 의존성
Constructor Injection에서는 멤버 객체가 순환 의존성을 가질 경우 BeanCurrentlyInCreationException이 발생해서 순환 의존성을 알 수 있게 된다. - 의존성 명시
의존 객체 중 필수는 Constructor Injection을 옵션인 경우 Setter Injection을 활용할 수 있다.
Lombok을 활용한 Constructor Injection 개발 편의성은 좋아질 수 있으나, 의존관계의 복잡성을 명확하게 보여주진 못하게 된다.
@RequiredArgsConstructor는 초기화 되지 않은 final 필드를 매개 변수로 취하는 생성자를 생성하고,
@NonNull이 필드는 null체크가 실행되고 파라미터가 null인 경우 NullPointerException을 발생시킨다.
Spring 4.3 이상
@RequiredArgsConstructor @Component public class ConstructorInjection { @NonNull private final LoginService loginService; @NonNull private final SignupService signupService; }
Spring 4.3 이전
@RequiredArgsConstructor(onConstructor = @__(@Autowired)) @Component public class ConstructorInjection { @NonNull private final LoginService loginService; @NonNull private final SignupService signupService; }
결론
- Constructor Injection과 Setter Injection을 적절히 사용하자.
- 클래스 하나에 너무 많은 책임을 위임하지 말자.
- Constructor Injection에 적절한 DI는 3개.
5개 이상 넘어간다면 클래스를 분리하여 역할을 분산한다.