IoC는 Inversion Of Control의 약자로 제어의 역전이라는 의미를 가지고 있다.
이는 복잡한 프레임워크 또는 기술에 의존하지 않아 코드 간의 결합도를 줄이고자 하는 스프링의 객체 지향적 이념을 실현하는데 중요한 역할을 한다
왜 복잡한 프레임워크에 의존하지 않는게 좋은 것일까? 그리고 스프링에 어떤 방식으로 어떤 장점을 가져올까? DI와 함께 그 이유를 살펴보자
스프링에서 의존성 주입이 왜 중요한 역할을 하는지 알기 위해서는 DI가 무엇인지 알아야한다. 코드로 살펴보자
첫 번째 코드
class Iphone extends Phone{}
class Galaxy extends Phone{}
class UsingPhone{
private Phone phone;
public UsingPhone(){
this.phone = new Iphone();
}
public call(){
this.phone.turnOn();
}
...
}
이 코드에서 call() 함수가 호출될 때 Iphone 객체를 필요로한다. 이처럼 UsingPhone 클래스가 Iphone의 의존성을 가지고 있는 경우에는 결합도가 높아진다.
이런 경우에는 UsingPhone클래스가 수정될 때 call() 함수도 수정해줘야한다. 만약 프로그램 내에서 call() 함수 하나만 Iphone 클래스 객체에 의존한다면 괜찮겠지만, 모든 프로그램이 결합도 높게 구현된다면 나중에 코드를 확장하기 힘들어진다.
두 번째 코드
class UsingPhone{
private Phone phone;
public UsingPhone(Phone phone){
this.phone = phone;
}
public call(){
this.phone.turnOn();
}
...
}
public Main{
public static void main(String[] args){
//UsingPhone의 객체를 생성할 때 필요한 Phone의 구현체를 삽입해준다
UsingPhone up = new UsingPhone(new Iphone());
}
}
이전 코드와 다르게 생성자 주입 방식을 사용해 필요한 객체를 주입해 주었다.
첫 번째 코드 처럼 코드 상에서 클래스를 직접 생성해 사용하는 것이 아니라 필요한 객체를 외부에서 주입하여 사용하기 때문에 객체 간의 결합도를 줄일 수 있다.
덕분에 상황에 따라 UsingPhone에 Galaxy, Iphone 또는 다른 Phone 객체를 주입해 줄 수 있고, 로직 수정 시 UsingPhone 클래스의 코드를 직접 수정하지 않아도 된다.
제어의 역전은 말그대로 프로그램에 필요한 메서드, 객체의 호출을 개발자가 결정하는 것이 아니라 외부에서 결정하는 것을 말한다. 이를 통해 객체간의 결합도를 줄이고 유연한 코드를 작성할 수 있다.
스프링은 DI를 통해서 IoC를 구현하였다. 스프링의 IoC 컨테이너를 통해서 위의 여러 장점들을 누릴 수 있지만 객체의 생명주기를 관리해주는 것이 기본 역할이다. 스프링의 등장 배경을 살펴보면 이전의 EJB보다 더 가볍고 복잡하지 않게 프로그래밍 할 수 있도록 POJO를 지향하는 프레임워크라는 것을 알 수 있다. 스프링에서는 IoC 컨테이너를 활용한 DI를 통해 POJO기반 개발을 가능하게 하고, 개발자가 객체의 생명 주기 등 세세한 것보다 비즈니스 로직에 집중할 수 있도록 도와준다. IoC 컨테이너가 빈 객체를 어떻게 관리하는지 자세히 알아보자.
POJO 클래스
객체 지향의 원리에 충실하며, 구체적인 환경과 기술에 의존하지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트

POJO 클래스들 중 어플리케이션에서 사용할 것을 고르고 이를 IoC 컨테이너에서 관리할 수 있게 메타 정보를 만들어 제공한다. IoC 컨테이너의 기본 역할은 빈 오브젝트를 생성하고 관리하는 것이다. 또한 빈을 어떻게 만들고 동작할 것인지에 대한 설정 메타정보를 만들어 제공한다.
이 메타 정보는 BeanDefinition 인터페이스로 추상화되며, 빈의 클래스, 속성, 의존 관계에 대한 정보를 가진다. 스프링에서는 XML, annotation, java config, properties 등 다양한 형식으로 이에 대한 설정을 지원하고 다양한 형식으로 이뤄진 설정 정보는 결국 BeanDefinition으로 변환된다
BeanDefinitionReader는 이 설정 파일들을 읽어 BeanDefinition 객체로 변환하는 역할을 한다(예를 들면, AnnotatedBeanDefinitionReader는 어노테이션 기반 설정을 읽는다).
BeanDefinitionReader는 빈 객체를 직접 생성하지 않지만 빈 생성에 필요한 메타 정보를 준비하는 중요한 역할을 한다.
그리고 IoC 컨테이너의 구현체인 ApplicationContext는 BeanDefinition 객체들을 사용해 실제 빈 객체를 생성하고, 의존성을 주입하고, 생명 주기를 관리한다. 그리고 더불어 여러 기능들을 지원한다
스프링 IoC 컨테이너의 다양한 기능 및 장점