이모저모

singleton container 본문

coding/Java, Spring

singleton container

Jeo 2022. 2. 16. 16:18

김영한님의 스프링 핵심원리 기본편 수강중!

🐦 웹 어플리케이션과 싱글톤(singleton) 디자인 패턴

  • 웹 어플리케이션은 여러 고객의 동시 요청이 많음
  • 매 요청시마다 AppConfig의 객체들을 새로 생성한다면 메모리 낭비가 심함.
  • 해결책 : 해당 객체가 1개만 생성되고 이후에는 공유/참조하도록 설계하는 "싱글톤(singleton) 패턴"

 

- 싱글톤이 적용된 서비스 예제

package hello.core.singleton;

public class SingletonService {

    // static 영역에 객체를 딱 1 개만 생성해둠.
    private static final SingletonService instance = new SingletonService();

    // public 으로 열어서 객체 인스턴스가 필요하면 이 static method 를 통해서만 조회하도록 허용
    public static SingletonService getInstance() {
        return instance;
    }
    // 생성자를 private 으로 선언해서, 외부에서 마음대로 new 키워드로 객체 생성하지 못하도록 막음.
    private SingletonService() {
    }

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

- 싱글톤 아닌 예와 싱글톤인 예시에 대한 test

package hello.core.singleton;

import hello.core.AppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

public class SingletonTest {

    @Test
    @DisplayName("스프링 없는 순수한 DI 컨테이너")
    void pureContainer() {
        AppConfig appConfig = new AppConfig();
        //1. 조회 : 호출할 때마다 객체를 생성
        MemberService memberService1 = appConfig.memberService();

        //2. 조회 : 호출할 때마다 객체를 생성
        MemberService memberService2 = appConfig.memberService();

        //참조값이 다른 것을 확인
        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);

        //memberService1 != memberService2
        assertThat(memberService1).isNotSameAs(memberService2);
    }

    @Test
    @DisplayName("싱글톤 패턴을 적용한 객체 사용")
    void singletonServiceTest() {
        SingletonService singletonService1 = SingletonService.getInstance();
        SingletonService singletonService2 = SingletonService.getInstance();

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

        // same : 정말 인스턴스가 같은지
        // equal : 값(?)이 같은지
        assertThat(singletonService1).isSameAs(singletonService2);
    }

}

🤔 싱글톤 패턴의 문제점

  • 싱글톤 패턴 구현을 위한 코드 자체가 더 필요하다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다( -> DIP를 위반)
    • getInstance()한 것을 불러와야 하기 때문.
    • 그렇기 때문에 OCP 원칙을 위반할 가능성 높음.
  • 이미 인스턴스를 만들어 가져오기 때문에 내부 속성을 변경하거나 초기화하기 어렵고, 유연하게 테스트하기 어렵다.
  • private 생성자를 사용하므로 자식 클래스를 만들기 어렵다.

 

- 그렇다면 우리가 AppConfig에서 모두 다 싱글톤 패턴을 적용해주어야 할까?

그러지 않아도 된다고 함! spring이 알아서 싱글톤패턴이 적용된 컨테이너를 생성해준다고 함.

 

🥸 스프링 싱글톤 컨테이너

  • 싱글톤 패턴의 문제점 모두 해결하면서 객체 인스턴스(bean)를 싱글톤으로 관리
  • 스프링이 알아서 싱글톤으로 동작시켜주는 것 확인
  • public class SingletonTest {
    
        @Test
        @DisplayName("스프링 컨테이너와 싱글톤")
        void springContainer() {
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    
            //1. 조회 : 호출할 때마다 객체를 생성
            MemberService memberService1 = ac.getBean("memberService", MemberService.class);
    
            //2. 조회 : 호출할 때마다 객체를 생성
            MemberService memberService2 = ac.getBean("memberService", MemberService.class);
    
            //참조값이 같음. 
            System.out.println("memberService1 = " + memberService1);
            System.out.println("memberService2 = " + memberService2);
    
            //memberService1 == memberService2
            assertThat(memberService1).isSameAs(memberService2);
        }
    }

🖍 싱글톤 패턴시 주의점!!

  • 공유 필드는 매우 매우 조심해야 함. 서로 다른 클라이언트가 동일한 인스턴스를 참조하고 이에 대해 필드를 공유하면, 결제 내역이 공유된다든가 나의 회원정보와 다른 내역이 공유된다든가 하는 아주 커다란 문제가 발생 가능!!
  • 그러므로 스프링 빈은 항상 무상태(stateless)로 설계해야 한다.
Comments