Dependency Injection (의존성 주입)
의존관계란? A가 B를 의존한다 -> B가 변하면 A에 영향을 미친다.
의존관계를 인터페이스로 추상화 -> 다양한 의존관계, 실제 구현 클래스와 관계 느슨해지고 결합도 낮아짐.
DI : 의존 관계를 외부에서 결정하고 주입하는 것. ¹객체 직접 생성 x, ²외부에서 생성 후 주입.
다음 세가지 조건을 충족하는 작업을 의존관계 주입 이라고 부름.
객체를 주입 받으면 결합도 낮추고, 런타임 시 의존관계 결정되기 때문에 유연한 구조.
의존성 주입 방식
@Controller
public class Controller {
@Autowired
private Service service;
}
인텔리제이 필드 주입 시 나오는 경고 -> Field injection is not recommended... Always use constructor based dependency injection in your beans.
@Controller
public class Controller {
private Service service;
@Autowired
public setService(Service service) {
this.service = service;
}
public void callService() {
service.doSth();
}
}
public interface Service {
void doSomething();
}
public class ServiceImpl implements Servce {
@Override
public doSth() {
System.out.println("do sth.");
}
}
public class Main {
public static void main(String[] args) {
Controller controller = new Controller();
// Service 인터페이스 구현하기만 하면 된다.
controller.setService(new ServiceImpl1());
controller.setService(new ServiceImpl2());
controller.setService(new Service() {
@Override
public void doSomething() {
System.out.println("Anonymous class is doing sth.");
}
});
// 어떻게든 구현체를 주입하고 호출하면 됨.
controller.callService();
}
}
@Controller
public class Controller {
private final Service service;
public Controller(Service service) {
this.service = service;
}
public void callService() {
service.doSth();
}
}
public class Main {
public static void main(String[] args) {
// Controller controller = new Controller(); // 컴파일 에러
Controller controller1 = new Controller(new ServiceImpl());
Controller controller2 = new Controller(
() -> System.out.println("Lambda implementation is doing something")
);
Controller controller3 = new Controller(new Service() {
@Override
public void doSomething() {
System.out.println("Anonymous class is doing something");
}
});
controller1.callService();
controller2.callService();
controller3.callService();
}
}
-> 생성자 주입 권장 이유?
필드/수정자 주입은 먼저 빈 생성 후, 주입하려는 빈 찾아서 주입함.
but 생성자 주입은 먼저 생성자의 인자에 사용되는 빈 찾거나, 빈 팩토리에서 빈 생성. 그리고 나서 주입하려는 빈 생성자 호출.
즉 먼저 빈 생성하는게 아니라 주입하려는 빈 먼저 생성.
ex) A 클래스가 B 클래스 의존, B 클래스가 C 클래스 의존.
A에 대한 빈 만들기 위해서 B를 주입해야 함.
B의 빈 주입해야 하는데 없으니까 B의 빈 생성. 근데 C의 빈 없으니까 C의 빈 생성.
즉 스프링은 C-B-A 순으로 빈 생성.
A->B, B->A 인 상황이면 A의 빈 만들때 B의 빈 주입해야 하는데, 없으니까 B의 빈 먼저 생성. 이때 A의 빈 주입해야 하는데 없으니까 A의 빈 먼저 생성. 이때 B의 빈 없으니까...무한 반복.
그렇기 때문에 생성자 주입 사용하면 스프링 애플리케이션 로딩 시점에 예외 발생!
주입 필요한 객체 주입 안되어도 객체 생성 가능한 것이 문제.
생성자 주입 사용시 의존관계 주입을 하지 않은 경우에는 Controller 객체를 생성할 수 없음.
즉 의존관계 내용을 외부로 노출시킴으로써 컴파일 타임에 오류 잡을 수 있음.
<참고>
생성자 주입 사용 이유 : https://yaboong.github.io/spring/2019/08/29/why-field-injection-is-bad/
https://n1tjrgns.tistory.com/230
https://jackjeong.tistory.com/41
전략패턴 : https://victorydntmd.tistory.com/292
DI, SOLID : https://imspear.tistory.com/153
순환 참조 : https://ch4njun.tistory.com/269
<생각해보기>