[Spring] 스프링의 싱글톤 패턴과 싱글톤 컨테이너에 대해 알아보자.

Spring은 왜 싱글톤을 고집할까?

Spring은 온라인 서비스 기술을 지원하기 위해 생겨났다.

대부분의 온라인 서비스는 웹 애플리케이션으로 이루어져있다.

요즘은 웹이 아닌 어플이 대세 아니야? 라고 할 수 있다.

하지만 대부분의 회사들은 회사를 소개하거나 문의를 받는 웹 페이지가 꼭 하나씩 있다.

그렇다고 앱을 만들지 못하는 것은 아니다.

필자가 앱을 개발해보진 않았지만 Kotlin + Spring을 사용하면 앱 서비스를 개발할 수 있다.

그렇다면 왜 Spring이 싱글톤을 고집하는가?

이 이유는 바로 웹 애플리케이션은 보통 여러 고객들이 동시에 요청을 보내기 때문이다.

 

싱글톤이란?

단 하나의 유일한 객체를 만들기 위한 코드 패턴이다.

똑같은 인스턴스가 필요할 때, 똑같은 인스턴스를 새로 만들지 않고 기존의 인스턴스를 가져와 활용하는

즉, 메모리 절약을 위한 패턴이다.

 

(아래 코드는 여러 싱글톤 생성 패턴 중 한 가지이다.)

class Singleton {

    private Singleton() {
    }

    private static class SingleInstanceHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingleInstanceHolder.INSTANCE;
    }
}

 

싱글톤 컨테이너?

스프링 컨테이너는 스프링 프레임워크의 핵심 컨테이너로 Bean(빈)의 생명 주기를 관리하며, Bean들의 DI(의존성 주입)을 관리해주고 이 Bean들을 싱글톤으로 관리한다.

(이 외에도 애플리케이션의 구성 요소를 관리하는 다양한 기능을 제공한다.)

Bean스프링 컨테이너에 저장된 자바 객체이다.

즉, 스프링 컨테이너에 Bean이 등록(자바 객체가 등록 )되면 이 Bean을 싱글톤으로 관리하는 것이다.

그리고 이 Bean들을 싱글톤으로 관리하는 스프링 컨테이너를 "싱글톤 컨테이너"라고도 한다.

 

스프링 컨테이너를 사용할 때와 아닐 때 비교해보기

이제 싱글톤을 적용하지 않았을 때와 적용했을 때를 비교해서 보자.

아래 코드는 의류 쇼핑몰 웹 사이트에서 옷을 주문하는 상황을 예시로 들었다.

 

옷 주문 로직을 관리하는 OrderService 클래스가 있고 옷 주문 정보를 괸리하는 테이블에 접근하는 OrderRepository 클래스가 있다.

그리고 이 의존성을 관리하는 OrderConfig 클래스가 있다.

 

싱글톤을 적용하지 않은 코드
@DisplayName("스프링 컨테이너를 사용하지 않는 경우. 싱글톤 X")
@Test
void useNotSingleton() {
    // given
    OrderConfig orderConfig = new OrderConfig();

    OrderService orderService1 = orderConfig.orderService();
    OrderService orderService2 = orderConfig.orderService();

    // when && then
    assertThat(orderService1).isNotSameAs(orderService2);
}

싱글톤이 적용되지 않은 상태에서 100만명의 고객이 옷을 주문하면 어떻게 될까?

동일한 100만개의 OrderService와 OrderRepository 객체가 생성된다.

하나만 있어도 될 객체가 100만개가 생성되어 버리는 것은 메모리 낭비가 너무 심하다.

그래서 스프링은 사용자의 요청 마다 객체를 생성하는 것이 아닌

이미 만들어진 객체를 공유해서 효율적으로 사용하도록 하기 위해 싱글톤 방식을 사용한다.

 

스프링 컨테이너를 사용해 싱글톤 적용한 코드
@DisplayName("스프링 컨테이너를 사용는 경우. 싱글톤 O")
@Test
void useSingleton() {
    // given
    ApplicationContext ac = new AnnotationConfigApplicationContext(OrderConfig.class);

    OrderService orderService1 = ac.getBean("orderService", OrderService.class);
    OrderService orderService2 = ac.getBean("orderService", OrderService.class);

    // when && then
    assertThat(orderService1).isSameAs(orderService2);
}

 

스프링 컨테이너에 Bean을 등록하는 방법은 여러 방법이 있지만

위 두 경우를 테스트하기 위해 AnnotationConfigApplicationContext 구현체의 인자에 @Configuration 어노테이션을 붙인 의존성 설정 파일OrderConfig.class를 넣어주는 수동 Bean 등록 방식을 사용했다.

참고로 스프링의 기본 Bean 등록 방식은 싱글톤이지만 요청할 때 마다 새로운 객체를 생성해 반환하는 기능도 제공한다.

이에 대한 내용은 Bean Scope(빈 스코프)에 대해 알아보는 것을 추천한다.

 

번외 - @Configuration의 비밀

이번 포스팅에서 수동 빈 등록을 사용하면서 @Configuration 어노테이션을 사용했다.

수동 빈 등록시에 꼭 이 어노테이션을 사용해야할까?

이 어노테이션에 대한 비밀은 아래 포스팅에서 자세하게 설명한다.

 

[Spring] 수동 빈 등록시 사용하는 @Configuration의 특별한 기능

위 코드를 보면 new 키워드로 객체를 반환하고 있다. 그렇다면 orderService()를 2번 호출하면 OrderService와 OrderRepository가 2번씩 생성되지 않을까? 결론부터 말하면 아니다. orderService()와 orderRepository()

hstory0208.tistory.com