의존성 주입(Dependency Injection, DI)은 소프트웨어 설계 원칙 중 하나로, 객체 지향 프로그래밍에서 객체의 생성과 객체 간의 의존성을 설정하는 과정을 분리하는 방법이다.
객체가 다른 객체를 필요로 할 때 새로 생성하기 보단, 기존에 있던 객체로부터 의존성을 주입시켜 객체간 결합도를 낮춘다.
의존성(Dependency)
한 객체가 필요로 하는 다른 객체를 의미
클래스 A가 클래스 B의 기능을 사용해야 하는 경우, B는 A의 의존성
주입(Injection)
의존성을 외부에서 제공하는 과정으로, 객체 A가 객체 B를 필요로 할 때 객체 B를 생성하고 A에 전달하는 과정을 의미한다.
필드 주입: 변수에 @Autowired
컴파일할 때 순환 참조 에러를 찾지 못하고 그 부분이 실행될 때 찾음
setter주입: 메소드에 @Autowired
자바의 다형성을 이용해서 하나의 변수에 여러 객체를 저장할 수 있는데, setter에 의해서 의존성 주입받은 객체가 바뀔 수 있음
컴파일할 때부터 순환 참조 에러를 찾을 수 있음
(Spring 4.3버전 이후부턴 생성자에 @Autowired 생략가능)
결합도 감소
객체가 다른 객체를 직접 생성하지 않고 외부에서 주입받기 때문에 클래스 간의 결합도가 낮아지며, 이는 코드의 유연성을 높이고, 변경에 더 잘 대응할 수 있게 한다.
재사용성 증가
의존성을 외부에서 주입받기 때문에 동일한 클래스를 다양한 컨텍스트에서 재사용할 수 있다.
테스트 용이성
의존성을 외부에서 주입받기 때문에, 단위 테스트 시 모의 객체(Mock Object)를 쉽게 주입하여 테스트할 수 있다.
두 개 이상의 빈이나 객체가 서로를 의존하는 상황을 의미하며 실행이나 컴파일 시 장애를 일으킬 수 있는 요인
Bean이 생성될 때, A는 B를 참조하고, 동시에 B는 A를 참조하는 경우(생성자로 주입)
@Component
public class A {
private final B b;
@Autowired
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private final A a;
@Autowired
public B(A a) {
this.a = a;
}
}
생성자로 의존성을 주입하게 되면 위의 코드일 경우 @Component가 붙은 클래스를 Bean으로 객체 생성 시도하는 시점(component-scan때 생성자를 호출하는 시점)에 무한 루프에 빠질 수 있다.
Bean A를 생성하려면 Bean B가 필요하고, Bean B를 생성하려면 Bean A가 필요한 상황이 발생하기 때문에 정상적으로 애플리케이션이 실행되지 못하고 문제가 발생하게 된다.
즉, 생성자로 의존성을 주입하게 되면 컴파일 시점에서 순환참조가 발생했음을 알 수 있다.
우선 객체 또는 Bean이 생성되고 런타임 시점에서 호출시 에러가 발생할 수 있음
어떤 방법이든 임시방편이기 때문에 구조 자체를 다시 짜는 것이 정론이다.
초기 개발시 고려할 점이 많아진다는 점에선 부정적 측면을 가지고 있다.
하지만 개발하려는 서비스의 규모를 확장하거나 이미 충분히 큰 규모의 서비스라면 유지보수나 테스트의 용의성, 확장성 등의 장점 때문에 사실상 필수 불가결한 선택이다.