"제어의 역전"이라고도 불리며, 객체 지향 프로그래밍에서 제어 흐름을 개발자가 아닌 프레임워크나 컨테이너가 관리하도록 하는 디자인 원리입니다.
스프링 프레임워크를 적용하기 전엔 객체의 생명 주기를 클라이언트 구현 객체가 직접 관리했습니다. 하지만 스프링을 사용하면 객체들의 로직은 우리가 직접 구현하지만, 해당 객체가 언제 호출되는지 신경 쓸 필요가 없습니다. 즉, 프로그램의 제어권이 역전된 것입니다.
"A가 B를 의존한다."
"A가 B에서 정의된 메서드를 사용한다."
"의존 대상 B가 변하면, 그것이 A에 영향을 미친다."
예를 들어 햄버거 가게 요리사는 햄버거 레시피에 의존한다. 햄버거 레시피가 변화하게 되면, 변화된 레시피에 따라 셰프는 햄버거를 만드는 방법을 수정해야 합니다.
때문에 셰프는 햄버거 레시피에 의존하고 있습니다.
class BurgerChef {
private HamBurgerRecipe hamburgerRecipe;
public BurgerChef(){
hamburgerRecipe = new HamBurgerRecipe();
}
}
또는
@Component
public class BurgerChef {
private final HamBurgerRecipe hamburgerRecipe;
// 생성자 주입 방식으로 의존성 주입
// IOC에 의해 스프링 컨테이너가 의존성 주입
@Autowired
public BurgerChef(HamBurgerRecipe hamburgerRecipe) {
this.hamburgerRecipe = hamburgerRecipe;
}
}
IOC/DI 개념에선 의존하고자 하는 객체를 더 이상 직접 생성하지 않습니다. 스프링 컨테이너가 생성된 인스턴스를 주입해줍니다.
DI를 통해 모듈 간의 결합도가 낮아지고 유연성이 높아집니다.
의존성 주입 방법으로 크게 3가지가 있습니다. 스프링에선 생성자 주입 방식을 추천하고 있습니다.
스프링에서 권장하는 방식
@RestController
class MyController {
private final MyService myService;
@Autowired
public MyController(MyService myService){
this.myService = myService;
}
}
@RestController
class MyController {
private MyService myService;
@Autowired
public void setMyService(MyService myService){
this.myService = myService;
}
}
선택적 의존성: 세터 방식은 객체 생성 후 의존성을 주입할 수 있기 때문에, 의존성이 선택적일 때 유용합니다. 예를 들어, 어떤 객체는 의존성 없이 생성할 수 있고, 나중에 의존성만 주입하면 되는 경우에 적합합니다.
수정 가능성: 세터 방식은 의존성 주입 후에도 변경이 가능하므로, 객체 생성 후에 의존성을 변경할 수 있는 유연성을 제공합니다. 이로 인해 한 번 주입된 의존성을 나중에 변경할 수 있는 상황에서 유리합니다.
불변성 부족: 객체 생성 후 메서드 호출 시 의존성이 주입되기 때문에 의존성이 주입되지 않거나 변경될 가능성이 있어 객체가 예기치 않게 변할 수 있습니다. 특히 final을 사용하여 불변 객체를 보장할 수 있는 생성자 방식에 비해 이 부분에서 약점이 있습니다.
@RestController
class MyController {
@Autowired
private MyService myService;
}
Reference
https://mozzi-devlog.tistory.com/18
https://tecoble.techcourse.co.kr/post/2021-04-27-dependency-injection/