스프링 컨테이너(Spring Container)

스프링에서 의존관계 주입(Dependency Injection, DI)을 이용하여 애플리케이션을 구성하는 여러 빈(Bean)들의 생명주기(Lifecycle)와 애플리케이션의 서비스 실행 등을 관리하며 생성된 인스턴스들에게 기능을 제공하는 것을 부르는 말이다.

 

컨테이너에 적절한 설정만 있다면 프로그래머의 개입 없이도 작성된 코드를 컨테이너가 빈을 스스로 참조한 뒤 알아서 관리한다.

 

스프링 컨테이너를 언급할때는 빈 팩토리(BeanFactory)와 애플리케이션 컨텍스트(ApplicationContext) 두 가지로 다룬다.

 

💡 의존관계 주입에 대해서는 IoC(Inversion of Control, 제어의 역전) / DI(Dependency Injection, 의존관계 주입)을 참고바란다.

 

💡 빈

스프링에서는 스프링이 제어권을 가져서 직접 생성하고 의존관계를 부여하는 오브젝트를 빈이라고 부른다.

 


빈 팩토리(BeanFactory)

빈을 생성하고 의존관계를 설정하는 기능을 담당하는 가장 기본적인 IoC 컨테이너이자 클래스를 말한다.

 

ApplicationContext

ApplicationContext는 BeanFactory를 구현하고 있어 BeanFactory의 확장된 버전이라고 생각하면 좋다.

 

💡 참고로 '빈 팩토리(BeanFactory)'라고 말할 때는 빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초점을 맞춘 것이고,

'애플리케이션 컨텍스트(ApplicationContext)'는 별도의 정보를 참고해서 빈의 생성, 관계 설정 등의 제어를 총괄하는 것에 초점을 맞춘 것이다.

 

BeanFactory vs ApplicationContext = ApplicationContext

스프링 공식 문서를 찾아보니 두 컨테이너 중 특별한 이유가 없다면 ApplicationContext를 사용해야 한다고 나와있다.

 

이유는 BeanFactory의 모든 기능을 포함하는 것은 물론이고 아래 추가 기능들을 제공하기 때문이다.

따라서 이 추가기능들을 사용하는 것보다메모리 소모량 축소가 더욱 중요한 상황이 아니라면 추가 기능을 위해 사용하는 것이 좋다. 추가기능 중 대표적인 것은 아래와 같다.

 

Environment

프로파일(Profile)을 설정하고 어떤 것을 사용할지 선택할 수 있게 해주며, 소스 설정 및 프로퍼티 값을 가져오게 해준다.

 

MessageSource

메시지에 대한 국제화(i18n)을 제공하는 인터페이스이다.
i18n은 internationalization(국제화)의 약칭으로 소프트웨어가 언어에 종속적이지 않고 한국어든 영어든 동시에 입력해서 사용할 수 있어야 하는 것을 만족시켜주는 것을 말한다.
메세지 설정 파일을 모아서 각 국가마다 로컬라이징을 함으로써 각 지역에 맞춤 메시지를 제공할 수 있는 것이다.


빈을 생성하는 방식 - 싱글톤 레지스트리

스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다.

 

매번 클라이언트에서 요청이 올 때마다 서버에서 각 로직을 담당하는 오브젝트를 새로 만든다고 가정해보자.

요청 한 번에 5개의 오브젝트를 새로 만든다고 가정했을 때 초당 500개의 요청이 들어오면 2500개의 새로운 오브젝트가 생성되고, 한 시간이면 9백만 개의 새로운 오브젝트가 만들어진다. 아무리 가비지 컬렉션(Garbage Collection)이 있다 하더라도 이것이 누적되면 그만큼 서버가 자원 소모를 많이 해야 하므로 비효율적인 방식이다.

그래서 필요한 것이 처음 한번 생성해두면 애플리케이션이 종료될 때까지 추가적인 생성 작업이 필요 없는 것인데, 이것이 싱글톤 방식이며 서버환경에서 권장되는 것이다.

 

💡 가비지 컬렉션(Garbage Collection, GC)에 대해 궁금하신 분들은 가비지 컬렉터(Garbage Collector) - 가비지 개념과 가비지 컬렉션 프로세스 포스팅을 참고 바란다.

 

💡 싱글톤 개념은 이런 오브젝트 생성 측면에서 자원 소모를 효율적으로 하기 위해 등장한 디자인 패턴에서 나온 것이다. 추후 별도의 포스팅 후 링크를 걸어둘 테니 참고 바란다.

 

디자인 패턴 중 '싱글톤(Singleton) 패턴'의 단점

  • 클래스 밖에서는 오브젝트를 생성하지 못하도록 생성자를 private으로 만든다.
    따라서 상속할 수 없으므로 객체지향의 장점인 상속과 이를 이용한 다형성(polymorphism)을 적용할 수 없다.
  • 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의한다.
    그런데 싱글톤은 사용하는 클라이언트가 정해져 있지 않으며, 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는 프로그래밍 모델이다.
  • 스태틱 팩토리 메서드인 getInstance()를 만들고 이 메서드가 최초로 호출되는 시점에서 한번만 오브젝트가 만들어지게 한다. 그런데 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
    서버에서 클래스 로더를 어떻게 구성하고 있느냐에 따라서 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있기 때문이다. 여러 개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생기기 때문에 싱글톤으로서의 가치가 떨어진다.
  • 그리고 싱글톤은 테스트하기도 힘들다. 만들어지는 방식이 제한적이라서 테스트에서 사용될 때 모의(Mock) 객체 등으로 대체하기가 힘들다. 초기화 과정에서 생성자 등을 통해 사용할 오브젝트를 다이나믹하게 주입하기도 힘들기 때문에 필요한 오브젝트는 직접 오브젝트를 만들어 사용할 수밖에 없다.

💡 클래스 로더(Class Loader)

자바 바이트 코드를 읽어서 JVM의 실행 엔진이 사용할 수 있도록 Runtime Data Area(JVM Memory)의 메서드 영역(Method Area)에 적재하는 역할을 시스템을 말한다. 클래스 로더에 대해 더 자세히 궁금하신 분들은 클래스로더(Class Loader) 포스팅을 참고 바란다.

 

💡 모의(Mock) 객체

테스트 시 호출했을 때 사전에 정의된 명세대로의 결과를 돌려주도록 미리 프로그램 돼있는 객체를 말한다. 모의 객체에 대해 더 자세히 궁금하신 분들은 테스트 대역(Test Double) 포스팅을 참고 바란다.

 

기존 싱글톤 패턴의 단점을 보완하는 싱글톤 레지스트리(Singleton Registry)

기존 싱글톤 패턴의 단점때문에 스프링에서는 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다.

이를 싱글톤 레지스트리(Singleton Registry)라고 한다.

 

이는 스태틱 메서드와 private 생성자를 사용해야 하는 비정상적인 클래스가 아니라 평범한 자바 클래스를 싱글톤으로 활용하게 해준다. 이 덕분에 public 생성자를 구현할 수 있어서 생성자 파라미터로 의존관계 주입이 가능하고, 테스트 시 모의 객체도 생성이 가능하다.

가장 중요한 점은 싱글톤 패턴과 달리 스프링이 지지하는 객체지향적인 설계 방식과 원칙, 디자인 패턴(싱글톤 패턴 제외) 등을 적용하는 데 아무런 제약이 없다는 점이다.


빈을 생성한 후 로딩하는 방식

Lazy-loading (BeanFactory의 방식)

메서드나 클래스가 빈 로딩 요청을 받는 시점에 인스턴스를 만들고 로딩하는 방법이다.

BeanFactory factory = new XmlBeanFactory(
            new InputStreamResource(
            new FileInputStream("oraclejavacommunity.xml"))); // 1
OracleJavaComm ojc = (OracleJavaComm)factory.getBean("oracleJavaBean"); // 2

1번에서 BeanFactory에 의해 oraclejavacommunity.xml 파일이 로드되더라도 어떠한 빈도 인스턴스화 되지 않고 2번에서 oracleJavaBean이 getBean() 메서드에 의해 요청을 받는 시점에 인스턴스를 만들고 되고 로드된다.

 

Pre-loading (Application Context의 방식)

모든 빈들과 설정 파일들이 ApplicationContext에 의해 로드 요청이 될 때 인스턴스로 만들어지고 로드된다.

ApplicationContext context = new ClassPathXmlApplicationContext("oraclejavacommunity.xml"); // 1
OracleJavaComm ojc = (OracleJavaComm) factory.getBean("oracleJavaBean"); // 2

1번에서 모든 싱글톤 빈들이 인스턴스화 된다. 그러므로 빈이 여러 개라면 시간이 소요될 것이다.

2번에서는 미리 컨테이너에서 만들어진 빈들이 클라이언트로 리턴된다.

 

이렇게 두 가지 방식으로 나눠둔 이유는 결국 사용빈도와 사용되는 자원의 관계 때문이다.

자주 사용되지 않는 빈이라면 소모되는 자원을 아끼기 위해 실제 요청 시에만 로딩하고(Lazy-loading), 자주 사용되는 빈이라면 한 번에 로드하는 것(Pre-loading)도 좋은 방법이다.


출처

빈 팩토리(BeanFactory)와 애플리케이션 컨텍스트(ApplicationContext)를 부르는 맥락상의 차이 부분,

싱글톤 레지스트리 부분

- 토비의 스프링 3.1

www.acornpub.co.kr/book/toby-spring3.1-vol1

 

Lazy-loading, Pre-loading 

ojc.asia/bbs/board.php?bo_table=LecSpring&wr_id=445

 

BeanFactory vs ApplicationContext

docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-introduction-ctx-vs-beanfactory