지난 글에서 객체의 생성을 책임지고 의존성 관리하는 IoC Container 그리고 DL, DI에 대해 알아보았다.
DI는 참 쉬우면서도 어려운 것 같다.
Spring은@Autowired
어노테이션을 이용하여 다양한 의존성 주입 방법을 제공하는데 이를 통해 객체간의 결합도를 느슨하게 만들고 코드의 재사용성을 높일 수 있다.
@Autowired
는 Spring에게 의존성을 주입하는 지시자 역할을 한다.
그럼 지금부터 의존성을 주입하는 세 가지 방법에 대해서 알아보자
- 생성자 주입(Constructor Injection)
- 필드 주입(Field Injection)
- 수정자 주입(Setter Injection)
@Controller
public class SampleController{
private final SampleService service;
public SampleController(SampleService service){
this.service = service;
}
}
별다른 어노테이션 없이 매개변수 생성자만 열어두면 사용 가능하다.
@Controller
public class SampleController{
@Autowired
private SampleService service;
}
필드에 @Autowired
어노테이션만 붙이면 자동으로 의존성 주입을 해준다.
@Controller
public class SampleController{
private SampleService service;
@Autowired
public void setSampleService(SampleService service){
this.service = service;
}
}
단점
이렇게 수정자 주입을 사용하면 setSampleService를 public으로 열어 두어야해서 언제 어디서든 변경이 가능하다.
Spring Framework Reference에서는 생성자를 통한 주입을 권장한다고 한다. 그 이유에 대해서 알아보자!
생성자 주입을 사용하면 순환 의존성 발생 시, BeanCurrentlyInCreationException으로 문제 상황을 알 수 있게 해준다.
개발을 하다보면 여러 컴포넌트 간 의존성이 생기는데
예를 들어 A가 B를 참조하고, B가 A를 다시 참조하는 순환 참조 코드가 있다고 가정하고 생각해보자
// A
public class A{
@Autowired
private B b;
public void hi_A(){
b.hi_B();
}
}
//B
public class B{
@Autowired
private A a;
public void hi_B(){
a.hi_A();
}
}
A의 hi_A 메소드를 실행시켰더니 b.hi_B();로 가라해서 B의 hi_B로 갔더니 이번엔 또 A의 hi_A로 가란다
이렇게 순환 참조가 일어나는 경우 필드 주입과 수정자 주입은 빈이 생성된 후에 참조를 하기 때문에 애플리케이션이 아무런 오류나 경고 없이 구동이 된다ㅡㅡ
그리고 이 것은 실제로 코드가 호출되기 전까지 그 문제를 알 수가없다.
하지만, 생성자를 통해 주입하고 실행하면 위에서 말한 것 처럼 BeanCurrentlyInCreationException이 발생하게 되고 이를 통해, 순환 참조도 알고~ 오류도 체크하고~
생성자로 의존성을 주입할 때 final로 선언할 수 있어 변경에 안전하다.
(OOP의 5가지 원칙 중 Open-Closed Principle을 생각해보자)
생성자 주입을 통해 변경의 가능성을 배제하고 불변성을 보장한다.
그리고 final로 선언한 생성자 주입 방식은 null이 불가능하다.
필드 주입은 의존성 주입이 굉장히 쉬워서 무분별하게 의존성을 주입할 수 있다.
그러면 하나의 클래스에서 지나치게 많은 기능을 하게될 수 있고 이는 "클래스는 한가지 책임만 가져야한다"라는 단일 책임 원칙(SRP)을 위배하게 된다.
생성자 주입을 사용하면 의존성을 주입해야 하는 대상이 많아질 수록 생성자의 인자가 늘어나는데 이를 통해 의존관계의 복잡성을 쉽게 파악할 수 있어 너!! 리팩토링 해야 해!!! 하고 싶게 알아 차릴 수 있다 ^_^ (리팩토링 실마리 제공)
생성자 주입은 생성자로 의존성을 주입받기 때문에 DI Container에 의존하지 않고도 의존성을 주입받아 사용할 수 있고, 이를 통해 코드 가독성이 높아지고, 유지보수 용이해지고 테스트의 격리성과 예측 가능성을 높인다.
항상 결론은 객체지향적인 설계와 구조를 생각하며 코드를 작성해야 한다는 것이다. 특히 오늘 알아본 Constructor Injection은 이러한 코드 작성이 가능하도록 해주는 방법인 것 같다.
나는 개발할 때 거의 Field Injection 방식을 사용했는데 (여기저기 다 필요할 때 마다 불러와서 사용했다.) 이후, 코드를 리팩토링하며 Constructor Injection으로 수정해보아야겠다!!!!!! 끄으으응ㅆ
참고
https://dev-coco.tistory.com/70
https://tecoble.techcourse.co.kr/post/2020-07-18-di-constuctor-injection/