스프링을 사용할 때 만들어야 할 주문서대로(xml 설정) 객체를 담아야 하는데, 소프트웨어상 그 객체들을 담는 공간을 일반적으로 컨테이너라고 합니다.
컨테이너는 보통 인스턴스의 생명주기를 관리, 생성된 인스턴스들에게 추가적인 기능을 제공합니다.
public class A{
private B b;
public A(){
this.b = new B();
}
}
public class A{
private B b;
public A(B b) {
this.b =b;
}
}
메소드나 객체의 호출 작업을 개발자가 결정하는 것이 아니라, 외부에서 결정되는 것을 의미합니다.
역할과 구현을 분리해 응집도를 높이고 결합도를 낮추며 이에 따라 개발자는 필요한 부분을 개발해서 끼워 넣기의 형태로 유연한 개발이 가능합니다.

위의 예시에서 객체를 만들기 위해서는 먼저 A를 생성하고, 그 다음으로 A에 주입할 B 객체를 만드는 방식으로, 구현 객체가 프로그램의 제어 흐름을 스스로 조종한다.
"객체를 수정하기 위해 큰 변경이 필요하다."

위 그림은 Ioc컨테이너를 적용한 예시입니다. 해당 경우에서는 작은 부품인 D를 생성하고, 이를 C에 주입하며, 부품 사이의 의존성이 큰 부품으로 역방향으로 진행됩니다.
프로그램이 제어 흐름을 직접 제어하는것이 아닌 외부에서 관리하는것
"객체를 수정하기 위해 큰 변경이 필요없습니다. 변경에 자유롭다"
DIP( 추상화에 의존해야지 구체화에 의존해서는 안된다)의 원칙을 따르는 원칙중 하나 입니다.
IOC 를 구현하는 방법중 하나 입니다.
메인 모듈(main mudule)이 ‘직접’ 다른 하위모듈에 대한 의존성을 주기보다는 중간에 의존성 주입자(dependency injector)가 이 부분을 가로채 메인 모듈이 ‘간접’적으로 의존성을 주입하는 방식입니다.
이를 통해 메인 모듈과 하위모듈간의 의존성을 조금 더 느슨하게 만들 수 있으며 모듈을 쉽게 교체 가능한 구조로 만듭니다.
A가 B에 의존한다. = B가 변하면 A에 영향을 미치는 관계 = A - > B
를 의미하며 코드로는 이러한 것을 A가 B에 의존한다고 합니다.

class B {
public void go() {
System.out.println("B의 go()함수");
}
}
class A {
public void go() {
new B().go();
}
}
public class main{
public static void main(String args[]) {
new A().go();
}
}
하지만 위의 방법 대로 하면 B를 수정할 경우 A에서도 수정을 해주어야 합니다. 그렇기 때문에

위의 방법 대로 하나의 모듈을 만들어 주고 그곳에 의존성을 주입해주면 됩니다.
B b = new B();
A a = new A();
a.setB(b);
B b = new B();
A a = new A(b);
외부에서 모듈을 생성하여 dev.add(new BackendDeveloper()) 이런식으로 집어넣는 구조가 되기 때문에 모듈들을 쉽게 교체할 수 있는 구조가 됩니다.
단위 테스팅과 마이그레이션이 쉬워집니다.
애플리케이션 의존성 방향이 좀 더 일관되어 코드를 추론하기가 쉬워집니다.
마이그레이션 : 다른 운영환경으로 이동하는 것(DB이동, 데이터 이동 등)
결국에는 모듈이 더 생기게 되므로 복잡도가 증가합니다.
종속성 주입자체가 컴파일을 할 때가 아닌 런타임 때 일어나기 때문에 컴파일을 할 때 종속성 주입에 관한 에러를 잡기가 어려워질 수 있습니다.
자바의 객체지향 개념에서, 인터페이스의 역할은 IoC와 DI와 같은 설계 원칙에 깊이 관련되어 있다는 것을 알 수 있었습니다.