[Spring 핵심 원리 이해] 4. 싱글톤 컨테이너

2024. 12. 27. 22:42·Spring

이전 글에서 우린 스프링 컨테이너가 어떻게 스프링 빈을 관리하고 사용하는지 알아보았다. 

스프링 컨테이너의 상속 관계와 스프링 빈을 조회하는 방법이 중심 내용이었다. 

 

이번 시간에는 싱글톤 컨테이너에 대해 알아보는 시간을 갖도록 하겠다. 

 

1. 싱글톤 패턴이란? 

AppConfig와 같이 DI 컨테이너는 사용자의 요청을 처리하는 역할을 수행한다. 그런데 요청하는 클라이언트가 하나가 아니라 여럿이라면 어떨까? 
현재 우리가 작성한 AppConfig는 요청이 일어날 때 마다 객체를 새로 생성하고 있다. 요청의 양이 많아지면 더 많은 객체를 생성해야 하기 때문에 효율적이지 않아 보인다.

반환되는 클래스의 인스턴스를 1개만 생성하도록 해 그 참조값을 반환 값으로 줄 수 있다면 더 효율적이지 않을까? 

이것이 싱글톤 패턴이다. 

 

싱글톤 패턴은 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다. 

그렇기에 싱긍톤 패턴을 완성하려면 객체의 인스턴스를 2개 이상 생성하지 못하도록 막아야 한다. 

private 생성자를 이용하면 new 키워드를 통해 인스턴스를 추가로 만들지 못하게 할 수 있을 것이다. 

 

package hello.core_review.singleton;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class SingletonService {

    private static final SingletonService instance = new SingletonService();


    public static SingletonService getInstance() {
        return instance;
    }

    private SingletonService() {

    }

    public void logic() {
        System.out.println("싱글톤 객체 로직 호출");
    }

    @Test
    @DisplayName("싱글톤 패턴을 적용한 객체 사용")
    public void singletonService() {

        SingletonService singletonService1 = SingletonService.getInstance();
        SingletonService singletonService2 = SingletonService.getInstance();

        System.out.println("singletonService1 = " + singletonService1);
        System.out.println("singletonService2 = " + singletonService2);

        Assertions.assertThat(singletonService1).isSameAs(singletonService2);

        singletonService1.logic();

    }
}

static 영역에 객체 인스턴스를 하나 만들어두고, 객체의 인스턴스가 필요할 때는 getInstance 메서드를 사용하도록 한다. 

그리고 private 생성자를 통해 외부에서 new 키워드를 사용하는 것을 막는다. 

 

테스트를 돌려보면 

 

똑같은 참조값을 가지고 있으며 테스트 또한 성공한 것을 볼 수 있다. 

이렇게 싱글톤 패턴을 사용할 수 있을 것이다.

 

그럼 싱글톤 패턴이 항상 좋을까?

이렇게만 보면 싱글톤 패턴은 구현하는데 코드를 더 작성해야 한다. 

또한 인스턴스를 받아야 하기 때문에 구체 클래스에 의존해야 해서 DIP를 위반하고 있다. 

같은 맥락으로 OCP 원칙도 위반할 가능성이 있다. 

그리고 유연성 또한 떨어진다. 

 

2. 싱글톤 컨테이너 

그런데 스프링 컨테이너는 이런 싱글톤 패턴의 문제점을 해결하면서 객체 인스턴스를 싱글톤으로 관리해준다. 

 

지금까지 우리가 학습한 스프링 빈이 싱글톤으로 관리되고 있는 빈이다. 

 

스프링 컨테이너는 따로 싱글턴 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다. 

스프링 컨테이너가 싱글톤 컨테이너의 역할도 수행하는 것이다. 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라고 한다. 

 

@Test
@DisplayName("스프링 컨테이너와 싱글톤")
public void springContainer() {

    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    MemberService memberService1 = ac.getBean("memberService", MemberService.class);
    MemberService memberService2 = ac.getBean("memberService", MemberService.class);

    System.out.println("memberService1 = " + memberService1);
    System.out.println("memberService2 = " + memberService2);

    Assertions.assertThat(memberService1).isSameAs(memberService2);
}

 

이렇게 따로 싱글톤 패턴을 적용하지 않았음에도 

 

같은 참조값을 공유하고 있음을 알 수 있다. 

 

 

3. 싱글톤 방식의 주의점 

하지만 싱글톤 패턴을 사용할 때 싱글톤 객체는 항상 무상태(stateless)로 설계해야 한다. 

 

무상태라는 것은 

특정 클라이언트에 의존적인 필드가 존재해서는 안되며

특정 클라이언트가 값을 변경할 수 있는 필드가 존재해서는 안된다는 것이다. 

가급적 외부에서는 읽기만 가능해야 하며

필드 대신에 지역변수나 파라미터, ThreadLocal등을 사용하는 것이 좋다. 

 

이는 동시성 문제와도 비슷해보인다. 같은 참조값을 사용하기 때문에 서로 다른 요청이 같은 참조값을 가진 객체의 값을 바꾼다면 논리적 독립성을 갖지 못하게 된다. 

 

package hello.core_review.singleton;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

public class StatefulServiceTest {

    @Test
    void statefulServiceSingleton() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

        StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
        StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);


        statefulService1.order("userA", 10000);
        statefulService2.order("userB", 20000);

        int price = statefulService1.getPrice();

        Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
    }


    static class TestConfig {
        @Bean
        public StatefulService statefulService() {
            return new StatefulService();
        }
    }
}

 

userA의 price는 10000이어야 할 것 같지만 테스트 결과는 20000원이다. 

 

 

그런데 이상한 점이 있다. 

AppConfig 코드를 살펴보면 memberService와 orderService모두 반환할 때 new 사용하는데, 이는 결과적으로 다른 2개의 MemoryMemberRepository가 생성되외 싱글톤 패턴이 지켜지지 않는 것처럼 보이기 때문이다. 

 

그런데 이들의 반환된 참조값은 모두 동일하다. 즉 같은 인스턴스를 공유하고 있다는 것이다. 

 

이것이 가능한 이유는 @Configuration 덕분이다. 

스프링 컨테이너는 싱글톤 방식을 보장해주어야 하기 때문에 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig를 상속받은 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한다. 

출처 - 김영한 스프링 강의

CHLIB 내부기술은 복잡하기 때문에 간단하게 어떻게 실행되는지만 확인해보면 

만약 반환하려는 인스턴스가 이미 스프링 컨테이너에 등록되어 있으면 그 인스턴스를 스프링 컨테이너에서 찾아서 반환해준다. 

그런데 없다면 새로 인스턴스를 생성해서 그것을 반환해주는 것이다. 

 

@Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 존재하지 않는다면 새로 생성해서 스프링 빈으로 등록하고 그 인스턴스를 반환하는 것이다. 

 

하지만 @Configuration이 적용되어 있지 않다면 @Bean 이붙은 메서드를 호출할 때 매번 새로운 인스턴스를 등록하고 반환한다. 즉 싱글톤 패턴이 보장되지 않는다. 

 

따라서 @Configuraion이 스프링 컨테이너의 싱글톤 패턴을 보장해주는 역할을 수행하는 것이다.

'Spring' 카테고리의 다른 글

[Spring 핵심 원리 이해] 6. 의존관계 자동 주입  (6) 2025.01.03
[Spring 핵심 원리 이해] 5. 컴포넌트 스캔  (3) 2024.12.28
[Spring 핵심 원리 이해] 3. 스프링 컨테이너와 스프링 빈  (9) 2024.12.20
[Spring 핵심 원리 이해] 2. 객체 지향 원리 적용  (6) 2024.12.18
[Spring 핵심 원리 이해] 1. 예제 만들기  (6) 2024.12.07
'Spring' 카테고리의 다른 글
  • [Spring 핵심 원리 이해] 6. 의존관계 자동 주입
  • [Spring 핵심 원리 이해] 5. 컴포넌트 스캔
  • [Spring 핵심 원리 이해] 3. 스프링 컨테이너와 스프링 빈
  • [Spring 핵심 원리 이해] 2. 객체 지향 원리 적용
코뮝
코뮝
  • 코뮝
    코뮝의 기술 블로그
    코뮝
  • 전체
    오늘
    어제
    • 분류 전체보기 (25)
      • DB (1)
      • JAVA (0)
      • 운영체제 (11)
      • Spring (9)
      • 알고리즘 (2)
      • 카테캠 3기 (1)
      • DevOps (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    프로그래머스 유연근무제
    카태켐3기
    기아 스레드
    카테캠 백엔드
    운영체제
    백엔드
    스프링
    자바
    원자 명령
    명품 운영체제
    가변 크기 할당
    고정 크기 할당
    참조의 지역성
    역페이지테이블
    OS
    스레드 매핑
    커널 레벨 스레드
    김영한
    분할 메모리 할당
    운영체제 공부
    기초 cs
    카태켐
    Java
    시스템 콜
    코스만 조건
    Spring
    스프링 기본
    타조 알고리즘
    생산자 소비자 문제
    CS
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
코뮝
[Spring 핵심 원리 이해] 4. 싱글톤 컨테이너
상단으로

티스토리툴바