소프트웨어 설계의 핵심은 변화에 유연하게 대응할 수 있는 구조를 만드는 것이다.
그중에서도 객체 지향 설계 원칙인 SOLID는 스프링(Spring) 철학의 기반이 된다.
이번 글에서는 그중에서도 IoC(Inversion of Control, 제어의 역전)과 DI(Dependency Injection, 의존관계 주입)를 중심으로 살펴본다.
좋은 객체 지향 설계의 5가지 원칙 (SOLID)
| SRP (단일 책임 원칙) | 한 클래스는 하나의 책임만 가져야 한다. |
| OCP (개방-폐쇄 원칙) | 확장에는 열려 있고, 변경에는 닫혀 있어야 한다. |
| LSP (리스코프 치환 원칙) | 부모 타입의 객체를 자식 타입으로 대체해도 정상 작동해야 한다. |
| ISP (인터페이스 분리 원칙) | 클라이언트에 맞는 여러 개의 구체적 인터페이스를 만들어야 한다. |
| DIP (의존관계 역전 원칙) | 고수준 모듈은 저수준 모듈에 의존하지 않아야 하며, 두 모듈 모두 추상화에 의존해야 한다. |
이번 포스팅에서는 특히 IoC와 DI를 중심으로 설명한다.
IoC (Inversion of Control) 제어의 역전
일반적으로 프로그램은 개발자가 객체의 생성, 제어, 의존성 관리까지 직접 담당한다.
하지만 IoC는 이 제어의 주체가 개발자에서 스프링 컨테이너로 역전되는 개념이다.
즉, 객체의 생성과 의존성 관리, 생명주기 제어를 스프링 컨테이너에게 위임함으로써
개발자는 비즈니스 로직에만 집중할 수 있게 된다.
스프링에서 IoC 컨테이너가 관리하는 객체를 **Bean(빈)**이라고 한다.
POJO (Plain Old Java Object)
POJO는 환경이나 기술에 종속되지 않은 순수한 자바 객체를 의미한다.
Spring은 이런 POJO를 기반으로 동작하도록 설계된 프레임워크이다.
Spring은 POJO 프로그래밍을 지향하며, IoC/DI, AOP, PSA 등의 기능을 통해
객체 지향 원칙에 충실한 코드를 작성할 수 있도록 지원한다.
Bean이란?
| Java Bean (POJO) | 필드는 private, 접근은 getter/setter로 제한된 순수 자바 객체 |
| Spring Bean | 스프링 IoC 컨테이너가 생성·관리하는 객체로, 개발자가 아닌 스프링이 제어권을 가진다. |
IoC 컨테이너의 구조
스프링의 IoC 컨테이너는 BeanFactory와 ApplicationContext로 나뉜다.

BeanFactory
- 스프링 컨테이너의 최상위 인터페이스
- Bean 생성, 조회, 의존성 주입 등 기본 관리 기능 제공
- 일반적으로 직접 사용하지 않고 ApplicationContext를 사용
ApplicationContext
- BeanFactory의 기능을 확장
- 이벤트 발행, 메시지 국제화, 환경 변수 관리 등 추가 기능 제공
- 실무에서는 대부분 ApplicationContext를 사용

// Annotation 기반으로 IoC 컨테이너 생성
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
IoC 컨테이너의 동작 흐름
IoC 컨테이너는 다음 순서로 애플리케이션을 구성한다.
객체 생성 → 의존성 주입 → 관계 설정 → 생명주기 관리
이때 “무엇을 만들고, 어떻게 연결할지”에 대한 정보를 담은 것이
**Configuration Metadata(설정 메타데이터)**이다.

빈을 등록할 때는 3가지 방법을 기반으로 객체를 빈으로 설정할 수 있다.
| XML 기반 | <bean> 태그로 Bean 정의 및 의존성 주입 |
| Annotation 기반 | @Component, @Autowired, @Bean, @Configuration |
| Java 기반 | @Configuration 클래스 + @Bean 메서드로 Bean 정의 |
BeanDefinition (빈 정의 메타데이터)
스프링 컨테이너는 설정 파일(XML, Java 등)에 상관없이
모든 Bean 정보를 BeanDefinition이라는 추상화된 형태로 관리한다.
즉, “이 객체가 어떤 빈이고, 어떤 의존성을 가지는가”만 알면
어떤 방식으로 정의되었든 동일하게 처리할 수 있다.

스프링은 이처럼 BeanDefinition 추상화를 통해서 다양할 설정 형식을 지원한다.
스프링 컨테이너 입장에서는 이게 xml인지 java인지 알 필요가 없다.
스프링 컨테이너는 해당 설정 정보를 BeanDefinition에서 읽어 설정에 맞게 생성하기만 하면 된다.
이제 스프링 컨테이너가 생성되고 빈을 등록, 의존 관계 주입등의 과정을 알아 보도록하자
스프링 컨테이너 생성
스프링 컨테이너를 생성할 때는 인자로 구성정보를 지정해서 줘야하고(AppConfig.class)
지정된 구성정보를 바탕으로 스프링 컨테이너를 생성하고 빈을 등록할 준비를 한다.

스프링 빈 등록
구성 정보를 바탕으로 스프링 빈으로 등록할 객체들을 찾아서 객체를 생성한다.

스프링 빈 생성완료
실제로 구성정보에 맞게 스프링 컨테이너에 스프링 빈이 생성되었다.
생성 이후에는 의존관계를 주입할 준비를 한다.

의존관계 설정 완료
해당 코드에 따라서 각 빈 별로 의존관계를 주입받으면 모든 과정이 끝났다.

이제 위 과정을 통해 스프링 컨테이너를 생성하고 스프링 빈을 컨테이너에 등록을 했다.
그 이후 애플리케이션이 시작되면서 스프링 컨테이너가 스프링 빈의 생명주기를 관리한다.