[스프링 기본] 싱글톤(1)

마코레·2022년 5월 13일
0

백엔드개발

목록 보기
14/18

🤗 인프런 [스프링 핵심원리-기본편]을 듣고 기록하는 글입니다

싱글톤이란


웹 애플리케이션과 싱글톤

  • 스프링은 기업용 온라인 서비스 기술 지원을 위해 탄생
  • 스프링앱은 대부분 웹 애플리케이션임
  • 웹 애플리케이션은 주로 여러 고객동시에 요청을 함

객체가 너무 많아진다!

  • 지금 우리가 만든 코드에서는 memberService 요청 시에 new memberService를 해서 반환
  • 고객수만큼 계속 객체를 만들게됨

해결방법

  • 객체를 딱 한개만 생성하고, 생성된 객체를 사용자끼리 공유하도록 설정하기


싱글톤 패턴


개념

클래스의 인스턴스가 딱 1개만 생성되는것을 보장하는 디자인 패턴

  • 하나를 만들고 난 후에는 새로 만들지 못하도록 막으면됨
  • 생성자를 private으로 만들어서 막고, 해당 클래스 안에서 만든것만 호출할 수 있게끔 getInstance() 함수 하나 만들어주기
  • 객체 하나 생성하는것보다 당연히 instance를 참조하는게 값이 덜나감

싱글톤 패턴은 여러가지 방식으로 만들 수 있으며, 지금 보여주신 방법은 객체를 미리 생성해두는 가장 단순하고 안전한 방법.

문제점

  1. 싱글톤 패턴 구현하는 코드 자체가 많아짐
  2. 클라이언트가 구체 클래스에 의존하게됨
  3. 그러므로, DIP OCP 원칙 위반할 가능성 큼
  4. 테스트가 어려움
    • 싱글톤은 인스턴스를 미리 받아서 설정이 끝난거임
    • 유연한 테스트가 어려움
  5. 내부 속성 변경이나 초기화가 어려움
  6. private 생성자이므로 자식 클래스 만들기 어려움
  7. 유연성 떨어짐
  8. 안티패턴으로 불리기도함

싱글톤 컨테이너


  • 스프링 컨테이너
    • 싱글톤 패턴의 문제점 해결
    • 동시에 싱글톤의 장점 유지
      • 객체 1개로만 유지
      • 코드 안더러움
      • DIP, OCP 지킴
      • 테스트 유연
      • private 생성자 필요없음
    • 싱글톤 패턴을 적용하지 않아도, 알아서 객체 instance를 싱글톤으로 관리
    • 스프링 컨테이너 = 싱글톤 컨테이너
    • 싱글톤으로 객체 생성 관리하는 기능 = 싱글톤 registry 라고 부름

싱글톤 방식의 주의점


  • 여러 클라이언트가 하나의 객체 instance를 공유

  • stateful하게 설계하면 절대 안되고 stateless로 설계!!
    - 특정 client에 의존적인 필드 NO

    • 특정 client가 변경할 수 있는 필드 NO
    • 읽기만 가능한게 제일 좋음
    • java에서 공유되지 않는 지역변수/parameter/ThreadLocal등을 사용하기
  • 스프링 bean의 field에 공유값 설정하면 큰 장애가 생길 수 있음!!

stateless 하게 유지하는 법

  • 객체의 값을 넘겨줄때, 객체 자체를 넘기지말고, 값을 넘겨주는 방식!
  • 예를 들어 user정보 넘겨줄때, User user를 넘기지 말고 int userId를 넘기기

싱글톤 실습코드


1. 스프링을 사용하지 않은 순수 DI - 싱글톤 보장안됨

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

  //항상 테스트는 자동화를 해야하기 때문에 위에처럼 print로 확인하면 안됨
  Assertions.assertThat(memberService1).isNotSameAs(memberService2);
  }

2. 싱글톤 패턴 구성

void singletonServiceTest() {
new SingletonService();
}

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

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

        assertThat(singletonService1).isSameAs(singletonService2);
        //same == 진짜 객체 인스턴스가 같은지
        //equal == java의 equal처럼 값이 같은지
    }

3.싱글톤의 문제점 코드

//서비스 코드
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 statefulServiceSingleton() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);

        //사용자 A가 주문하고 금액 조회하는 사이에 다른 사용자가 주문을 한 상태
        //ThreadA: A사용자 1000원 주문
        statefulService1.order("userA", 10000);
        //ThreadB: B사용자 2000원 주문
        statefulService2.order("userB", 20000);

        //ThreadA: 사용자A 주문 금액 조회
        //서로 다른 service객체 썼음에도 불구하고 조회시에 B로 나옴
        int price = statefulService1.getPrice();
        System.out.println("price = " + price);

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


    }

    //테스트 전용 test config
    static class TestConfig {
        @Bean
        public StatefulService statefulService() {
            return new StatefulService();
        }
    }
}

  • 실제 스레드를 사용한 예제는 아님
  • price라는 필드가 공유되는 필드임. 그래서 20000원이 나온것
  • 이런식으로 공유되는 필드는 spring을 stateful하게 만듦.
  • spring은 되도록 stateless로 유지하기
  • 예를 들어, 굳이 class안에 local변수만들어서 거기에 저장했다가 넘기지말고,
    받은 그대로 return해주기
  • 글고 이런식으로 하면 남의 아이디가 보이는등의 문제발생
profile
새싹 백엔드 개발자

0개의 댓글