어떤 개념일까?

스프링 빈의 설계 철학

스프링 빈은 객체 생성, 조립, 생명주기의 통제권을 객체가 아니라 외부 컨테이너로 옮긴 결과물이고, 통제권의 이전의 목적은 DIP 의존성 역적 원칙을 코드가 아니라 인프라 수준에서 강제하여 결합도를 낮추는 것이다.

스프링 빈은 스프링 IoC 컨테이너가 인스턴스화, 조립, 생명주기 관리를 담담하는 객체이고, 공식문서 상 IoC 컨테이너의 핵심 역할은 다음과 같은 3가지가 된다.

  1. 빈 생성
  2. 의존성 주입
  3. 생명주기 관리

다음의 스프링 빈 설계 철학에 대한 개념 설명이다.

IoC (Inversion of Control, 제어의 역전)

객체가 자기 의존성은 직접 생성하거나 찾지 않고, 프레임워크가 흐름의 제어권을 가지고 코드를 호출하는 원칙으로, 객체 생성 통제를 컨테이너로 역전시킨다.

DI (Dependency Injection, 의존성 주입)

IoC를 구현하는 구체적인 방식으로, 객체가 협력 객체를 오직 생성자 인자, 세터로 선언하고, 컨테이너가 빈을 만들 때 그 의존성 주입하게 한다.

싱글톤으로 무상태 설계

빈이 기본적으로 싱글톤으로 만들어지는데, 싱글통 무상태로 하면 협력 객체를 매번 새로 만들 필요 없어 메모리, 생성 비용을 아끼기 위함이다. 즉, 싱글톤이 안전한 이유는 빈이 상태를 갖지 않도록 설계가 되어 있기 때문이다.


어떤 문제를 해결하려고 나왔을까? 왜 사용 할까?

기존 객체지향의 구조적인 한계

기존에 순수 자바만으로 만들었다고 생각해보면, OOP 객체지향적으로 다형성을 추구하는 설계를 만들고자 하지만, 순수 자바만으로 그 이상을 다 지키기 어렵다. 다음과 같은 문제점들이 존재한다.

  • 구체 클래스에 의존하게 되어 OCP가 위반된다.
  • 가짜 테스트 구현체를 주입할 방법이 없어 테스트가 어렵다.

즉, 객체가 자기 의존성을 스스로 생성하게 된다.

스프링이 한계를 해결한 방법

스프링은 의존성을 누가 결정하고 연결하는 가를 객체에서 분리하여 외부 컨테이너에게 넘기게 한다. 객체에서 생성자 매개변수를 통해 받고, 추상화에 의존하게 만들고, 이에 대한 제어권을 컨테이너에게 넘겼다.

  • DI로 생성의 책임을 컨테이너로 이전한다.
  • IoC 제어의 역전으로 제어권을 프레임워크로 역전한다.
  • DIP 의존성 역전 원칙을 컨테이너가 인프라로 강제한다.
  • 빈 관리 (스코프, 생명주기)를 스프링이 해결하도록 위임한다.

어떻게 동작하나?

IoC 컨테이너의 빈 생성 순서

ApplicationContext가 IoC 컨테이너의 구현체이고, 다음과 같은 단계로 빈을 생성한다.

  1. 설정 메타데이터 읽기: @Component, @Service, @Repository, @Controller 어노테이션들을 스캔하고, BeanDefinition으로 내부에서 변환한다.
  2. 빈 인스턴스화: 정의에 따라서 객체들을 생성한다.
  3. 의존성 주입: 생성자, 세터로 협력하는 빈들의 의존관계를 주입한다.
  4. 생명주기 콜백: 초기화 (@PostConstruct) 후 사용하고 소멸 (@PreDestroy) 관리

왜 빈이 싱글톤일까

스프링 빈은 기본적으로 singleton으로 만들어진다. 즉, 컨테이너당 하나의 인스턴스만 만들어서 캐시에 저장하고, 같은 이름의 빈 요청은 모두 그 캐시된 객체를 반환한다. 그 이유는 다음과 같다.

  • 메모리 효율: 요청마다 서비스를 새로 만들게 되면 GC 생성 비용이 누적되어 비용이 증가한다.
  • 무상태 서비스 공유가 가능하다: 상태를 갖지 않아서 멀티 스레드 환경에서 같은 인스턴스를 써도 안전하다.

멀티스레드 환경에서는 자바 힙에서 모든 스레드가 공유하는 힙에 메모리를 저장하게 되는데, 싱글톤 빈은 단 하나로 저장되게ㅗ, 동시 요청을 처리하는 여러 스레드에서 결국 같은 빈 인스턴스에 참조 접근을 하게 된다. 힙 레벡에서는 락이 없으므로 스레드들은 그 객체의 메서드를 동시에 실행 할 수가 있다.

그래서 빈이 만약 인스턴스 필드 공유 가변 상태를 가지게 된다면 여러가지의 동시성 race condition 데이터 오염 문제들이 생기게된다.

그래서 싱글톤 빈이 지켜야 하는 것들이 다음과 같은 것들이 존재한다.

  • 동일 인스턴스를 모든 스레드가 공유하면서도 상호 간섭이 없는 무상태(불변, 스레드 safe)로 설계
  • 싱글톤 빈은 절대 synchronized로 막지 않는다. 락을 걸게 된다면 단일 인스턴스 병목으로 인해 처리량이 무너지게 된다. 동기화로 해결하는게 아니고, 상태를 제거하고 무상태로 만들어서 해결하는 것이다.

언제 쓰고, 언제 안 쓰나?

쓸 때:

안 쓸 때:


남에게 설명한다면 어떻게 설명할 것인가?


추가 궁금한 질문들