[Spring] 스프링 싱글톤과 싱글톤 컨테이너 란?

JJoSuk·2023년 6월 1일
0

본 프로젝트 자료는 김영한님의 스프링 핵심 원리 - 기본편 참고 제작됐음을 알립니다.

1. 웹 애플리케이션과 싱글톤

  • 스프링은 태생이 Enterprize Online Service Web App(기업용 온라인 서비스 기술)을 지원하기 위해 탄생했다.
  • 보통의 기업용 웹 서비스는 여러 고객이 동시에 사용하는 서비스로 많은 요청이 처리된다.
  • 만약 동시 다발적으로 수 많은 요청이 발생하는 상황에 요청마다 인스턴스를 만드는 멀티톤 방식대로 동작하게 둔다면, 초당 감당할 수 있는 메모리는 그걸 넘어서는 데이터가 들어오는 순간(OOM 발생) 서비스에 큰 장애를 발생시키고 서버는 먹통이 될 것이다.
  • 그러니 우리는 단일 인스턴스를 가지는 싱글톤 패턴을 적용해야 한다.
  • 스프링은 이러한 상황을 고안해 만들었기에 스프링 컨테이너 자체가 하나의 인스턴스만을 보장한다.
  • 싱글톤 패턴으로 구현하는 경우, 효율적인 메모리 사용이 가능한 장점을 가지고 있다.
  • 하지만 공유자원도 동시성 문제에 취약하기에 이를 대비해서 잘 설계해야 한다.


2. 싱글톤 컨테이너

싱글톤 컨테이너란 클래스의 인스턴스가 Java JVM 내의 단 하나만 존재하는 것을 뜻 한다. 그래서 객체 인스턴스가 2개 이상 생성하는걸 막아야 하는데, 그 부분을 private 생성자를 사용해서 강제로 단일로 사용하게 막을 수 있다.

관련 코드

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance() {
        return instance;
    }

    private SingletonService() {

    }

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

하지만 이 방식으로 작성할 경우 많은 불편함과 문제점이 생긴다.

Java 싱글톤 패턴 구현시 문제점

  • 싱글톤 패턴 구현 코드가 요청할 때 마다 새로 생성해야 해서 많은 들어가며 코드 작성하는데 불편함이 있다.
  • 의존관계상 클라이언트가 구체 클래스에 의존 -> DIP 를 위반한다.
  • 클라이언트가 구체 클래스에 의존 -> OCP 원칙 또한 위반할 가능성이 높다.
  • 테스트마다 데이터를 초기화를 해야해서 까다롭다.
  • 내부 속성을 변경하거나 초기화 하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.
  • 결론적으로 유연성이 떨어진다.
  • Anti-패턴으로 불리기도 한다.

3. Spring 싱글톤 컨테이너

스프링 컨테이너는 위와 같은 기존의 싱글톤 패턴의 문제점을 해결해준다.
전에 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이라는 점을 참고.

  1. 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
  2. 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리 라고 불린다.
  3. 스프링 컨테이너에 단일 인스턴스만을 가지게 하는 기능 덕에 기존의 문제점을 해결하면서 싱글톤을 유지할 수 있다.
  4. 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.
  5. DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있다.

관련 코드

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

    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);

    assertThat(memberService1).isSameAs(memberService2);
}

싱글톤 컨테이너 적용 후 모습

위 예제 코드 덕에 사용자의 요청이 올 때 마다 객체를 생성하는게 아니라, 이미 만들어진 객체를 공유함으로서 효울이 올라간 모습을 볼 수 있다.


4. 싱글톤 방식의 주의점

싱글톤 방식 자체가 하나의 객체 인스턴스를 생성해 많은 사용자들이 공유 및 제사용으로 트레픽을 줄이는 목적을 사용하지만 생성할 때 와 설계할 때 용도를 잘 파악하고 거기에 맞춰서 설계해야한다.

관련 이유는 만약 결제 관련된 프로그램이 싱글톤 객체 상태를 stateful(유지상태)하게 설계된다면 큰일(?)이 발생한다고 한다.

관련 문제점 예시) 설계 코드

public class StatefulService {

    private int price; // 상태를 유지하는 필드

    public void order(String name, int price) {
        System.out.println("name = " + name + " price = " + price);
        this.price = price; // 여기가 문제!
    }

    public int getPrice() {
        return price;
    }
}

주문 정보 생성 프로그램 코드

class StatefulServiceTest {

    @Test
    void statefulServiceSingletin() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);

        // ThreadA: A사용자 10000원 주문
        statefulService1.order("userA", 10000);
        // ThreadB: B사용자 20000원 주문
        statefulService2.order("userB", 20000);

        // ThreadA: A사용자 주문 금액 조회
        int price = statefulService1.getPrice();
        System.out.println("price = " + price);

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

    static class TestConfig {

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

위 코드 작성대로 실행한다면 아래와 같은 문제점이 생긴다.

  • ThreadA가 사용자A 코드를 호출하고 ThreadB가 사용자B 코드를 호출한다.
  • 하지만 유지 상태이기에 ThreadA 와 ThreadB 는 같은 정보를 공유한다.
  • 그래서 사용자A 가 10000원 짜리를 주문하고 그 뒤로 사용자B 가 20000원 짜리 물건을 주문하게 된다면, 값이 공유받게 되어 사용자A 가 주문한 물품 금액인 10000원이 20000원으로 변경하게 된다.
  • 이런 공유필드는 항상 조심해서 사용해야 하며, 안전하게 무상태로 설계하도록 하자.

Spring 2일차

profile
안녕하세요

0개의 댓글