
일반적으로 소프트웨어에서는 개발자가 코드를 작성하고 호출하는 방식으로 프로그램의 흐름을 제어합니다. 하지만 제어의 역전이 이루어지면 제어흐름이 외부 요소(프레임워크)로 이동합니다.
예를들면 제어의 역전으로 인해 개발자가 객체의 생명주기를 관리하는게 아니라
객체의 생명주기는 외부요소인 외부 컨테이너나 프레임워크에 의해 관리됩니다.

(출처 스프링 공식문서)
스프링 컨테이너는 제어의 역전을 구현하는 주체입니다. 스프링은 등록된 컴포넌트들의 의존성을 주입하고, 컴포넌트들의 라이프사이클을 관리합니다.
따라서 컴포넌트들은 개발자가 작성한 코드이지만, 그들의 관리와 실행은 스프링 컨테이너에 의해 이루어집니다.
개발자는 스프링의 설정 파일(XML 또는 JavaConfig)이나 어노테이션을 사용하여 컴포넌트들을 스프링 컨테이너에 등록합니다.
그 예로 @Controller와 @Service등이 있습니다.
동작은 개발자가 코드를 작성해서 구현하지만, 그 컴포넌트들이 언제 호출되고 언제 소멸될지 고려할 필요가 없습니다. 스프링 컨테이너가 필요한 상황에 맞게 생성하고 호출하고 소멸하기 때문입니다. 프로그램의 제어권이 프레임워크에 있는 것이죠.
이러한 제어의 역전을 이루는 방법으로는 DI,DL등 여러 방법이 있습니다.
필요로 하는 의존 객체를 직접 생성하는 것이 아니라 외부에서 주입받는 것을 말합니다.
의존성이란 객체가 다른 객체에 의존한다는 의미 입니다.
그래서 A가 B에 의존하는 경우, B가 변경되면 A또한 변경되어야 할 수 있습니다.
코드로 보면 A코드가 B코드에 의존하면 B코드를 변경해야할때 A코드를 고려하거나, B코드를 변경하고 A코드까지 변경해줘야 할수 있습니다.
그럼 코드의 유지보수적 측면에서 많은 시간과 비용이 소모될 수 있습니다.
이러한 문제점을 해결하기 위해 의존성 주입을 사용할 수 있습니다.
의존성 주입을 통해 A 객체는 직접 B 객체를 생성하거나 제어하지 않고 외부에서 B 객체를 주입받아 사용합니다.
그렇기 때문에 B 객체가 변경되더라도 A 객체는 영향을 받지 않을 수 있습니다.
의존성 주입을 하는 방법에는대표적으로 생성자를 통한 주입과 setter를 통한 주입이 있습니다.

이 코드를 보면 의존성 주입을 사용하여 SimpleMovieLister 객체가 생성될 때,
MovieFinder 객체가 주입되도록 하기 위해 생성자가 정의되어 있습니다.
생성자를 통해서 매개변수로 객체를 주입받아 의존성을 주입하는 것입니다.

SimpleMovieLister 클래스는 MovieFinder 필드를 가지고 있으며,
setMovieFinder 메서드를 통해 외부에서 MovieFinder 객체를 받아서 해당 필드에 설정합니다.
이렇듯 의존성주입을 통해 결합도를 낮추면 의존성 주입을 통해 객체 간의 결합도가 낮아지기때문에 코드 변경이나 기능 추가 시에도 다른 클래스에 영향을 덜 주고 해당 클래스만 수정할 수 있습니다.
또 테스트용 객체나 모의(mock) 객체를 주입하여 테스트를 진행할 수 있기 때문에 테스트코드가 간편해집니다.