Dependency Injection

우리가 지금까지 써왔던 Field Injection Type. 왜 바꿔야 할까?

https://www.petrikainulainen.net/software-development/design/why-i-changed-my-mind-about-field-injection/

스프링에서의 소개

https://spring.io/blog/2007/07/11/setter-injection-versus-constructor-injection-and-the-use-of-required

– 번역

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을 권장하나?

  1. 단일 책임의 원칙
    생성자의 인자가 많을 경우 코드량도 많아지고, 의존관계도 많아져 단일 책임의 원칙에 위배된다.
    그래서 Constructor Injection을 사용함으로써 의존관계, 복잡성을 쉽게 알 수 있어 리팩토링의 단초를 제공하게 된다.
  2. 테스트 용이성
    DI 컨테이너에서 관리되는 클래스는 특정 DI 컨테이너에 의존하지 않고 POJO여야 한다.
    DI 컨테이너를 사용하지 않고도 인스터화 할 수 있고, 단위 테스트도 가능하며 다른 DI 프레임워크로 전환할 수도 있게 된다.
  3. Immutability
    Constructor Injection에서는 필드는 final로 선언할 수 있다.
    불변 객체가 가능한데 비해 Field Injection은 final을 선언할 수 없기 때문에 객체가 변경 가능한 상태가 된다.
  4. 순환 의존성
    Constructor Injection에서는 멤버 객체가 순환 의존성을 가질 경우 BeanCurrentlyInCreationException이 발생해서 순환 의존성을 알 수 있게 된다.
  5. 의존성 명시
    의존 객체 중 필수는 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개 이상 넘어간다면 클래스를 분리하여 역할을 분산한다.

자주 사용했던 코드

Stream과 Distinct를 활용한 중복제거

** List<Object>일 경우에는 Object의 hash가 다를 수 있으므로 distinct 하여도 중복된 값이 걸러지지 않을 수 있음.

private void checkDuplicated(List<Integer> list) {
  list = list.stream().distinct.sorted.collect(Collectors.toList());
  list.forEach(i -> System.out.print(i));
}

Map 내 Key, Value 체크가 필요할때

private void printKeyValue(Map<String, Object> map) {
   map.forEach((key, value) -> System.out.println(key + " : " + value + "\n"));
}