카테고리 없음

[spring] singleton (스프링 핵심 원리-기본편 from 김영한 강사님)

Lord DEVader 2023. 8. 28. 09:45
System.out.println("이 게시물은 김영한님의 스프링 핵심 원리-기본편을 참조하였습니다.");

 

싱글톤 (singleton)

    : SW 디자인 패턴중 하나.

      생성자가 여러 차례 호출되도 실제로 생성되는 객체는 하나이고,

      최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴.

    - 싱글톤의 문제점

        - 짧게 말해, 유연성 부족.

        - 클라이언트가 구체 클래스에 의존한다. => DIP 위배 => OCP 위배

        - 테스트가 어렵다.

        - 자식 클래스를 만들기 어렵다.

 

출처 - 싱글턴 패턴 - 위키백과, 우리 모두의 백과사전 (wikipedia.org)

 

싱글턴 패턴 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org


기존의 패턴

public class SingletonTest {

//("스프링 없는 순수한 DI 컨테이너")
    @Test
    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
        Assertions.assertThat(memberService1).isNotSameAs(memberService2);

        // 현재 객체를 사실상 4개 생성하는것이다.
    }
}

 

- 조회할때마다 객체를 새로 생성한다. ==> 시간과 자원을 많이 소모한다.


싱글톤 패턴

public class SingletonService {

    private static final SingletonService instance = new SingletonService();    //자기 자신을 static으로 하나 갖는다.

    public static SingletonService getInstance() {
        return instance;
    }

    private SingletonService(){
    }

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

}

- 클래스에서 static으로 선언한 변수는 유일하게 존재하고, 객체 사이에서 공유된다.

- 생성자를 private로 막아준다.

- getInstance() 메서드를 통해서만 조회할수 있게 한다.


스프링에서의 싱글톤

//"스프링 컨테이너와 싱글톤"
	@Test
    void springContainer() {
//        AppConfig appConfig = new AppConfig();
        ApplicationContext 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
        Assertions.assertThat(memberService1).isSameAs(memberService2);

    }

스프링 프레임워크는 싱글톤의 단점을 제거한 상태로 싱글톤을 사용한다.

 


싱글톤 방식의 주의점

    - 무상태로 설계해야 한다.

        - 특정 클라이언트에 의존적인 필드가 있으면 안되고,

          특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안됨.

          그리고, 싱글톤 객체는 상태를 유지하게 설계되면 안된다.

 

class StatefulServiceTest {

    @Test
    void statefulServiceSingleton() {
        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("userA", 20000);
        
        //ThreadA : 사용자A 주문 금액 조회
        int price = statefulService1.getPrice();
        System.out.println("price = " + price);
    }

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

}

- price가 적절치 않게 나옴

 

class StatefulServiceTest {

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

        //ThreadA : A사용자 10000원 주문
        int userAPrice = statefulService1.order("userA", 10000);
        //ThreadB : B사용자 20000원 주문
        int userBPrice = statefulService2.order("userA", 20000);
        
        //ThreadA : 사용자A 주문 금액 조회
        System.out.println("price = " + userAPrice);
    }

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

}

적절한 price값이 나옴.

 

 

 

[출처]

https://www.inflearn.com/course/lecture?courseSlug=%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8&unitId=55337&tab=curriculum