스프링 핵심원리 기본편13 - 싱글톤 컨테이너, 싱글톤 방식의 주의점
본문 바로가기

TIL/스프링 핵심원리 - 기본편 with 인프런(김영한님)

스프링 핵심원리 기본편13 - 싱글톤 컨테이너, 싱글톤 방식의 주의점

반응형

안녕하세요. 오늘은 싱글톤 컨테이너는 공부해 보겠습니다.

 

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

스프링 컨테이너는 싱글톤 컨테이너 역할을 하며 이러한 싱글톤 객체 생성 관리 기능을 싱글톤 레지스트라고 합니다.

 

스프링 컨테이너 사용 시 싱글톤 패턴을 위한 코드가 필요하지 않아 코드가 깔끔해집니다. 그리고 private 생성자로부터 자유롭게 싱글톤을 사용할 수 있습니다.

 

소스로 구현해 보면

1
2
3
4
5
6
7
8
9
10
11
12
        AnnotationConfigApplicationContext 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);
        
        //맴버서비스1,2는 같다.
        Assertions.assertThat(memberService1).isSameAs(memberService2);
cs

 

스프링 사용시 따로 소스로 구현해 줄 필요가 없이 위와 같은 코드면 전부 해결이 됩니다.

스프링 컨테이너 덕분에 고객의 요청에 따라 객체를 생성하는 것이 아니라 이미 만들어진 객체를 공유하여 효율적으로 사용합니다.

 

스프링의 기본 빈 등록 방식은 싱글톤이지만 요청할 때마다 새로운 객체를 생성하도록 하는 기능도 제공합니다.

해당 기능의 자세한 내용은 나중에 강의에서 설명해 주시면 정리해 보겠습니다.

 

다음으로 싱글통 방식의 주의점의 강의를 공부해 보겠습니다.

 

싱글톤 패턴 또는 스프링에서 사용하는 싱글톤 컨테이너를 사용하던 객체 인스턴스를 하나만 생성 공유하는 방식은 여러 사용자가 하나의 객체를 공유하기 때문에 객체는 상태를 유지하게 설계하여야 합니다. 즉 무상태로 설계해야 합니다.

무상태란

- 특정 클라이언트에 의존적인 필드가 존재하여서는 안된다.

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

- 가급적 읽기만 가능해야 한다.

- 필드 대신 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.

 

또한 스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있습니다.!!

 

코드를 예시로 보자면

1
2
3
4
5
6
7
8
9
10
    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;
    }
cs

먼저 test코드에 사용자 이름과 가격을 print 해주고 가격을 리턴하는 service를 만들어 줍니다.

 

다음으로

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
    void statefulServiceSingleton() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 =ac.getBean(StatefulService.class);
        
        //ThreadA
        statefulService1.order("userA"10000);
        //ThreadB
        statefulService1.order("userB"20000);
        
        int price = statefulService1.getPrice();
        System.out.println("price : " + price);
    }
    
    static class TestConfig{
        @Bean
        public StatefulService statefulService() {
            return new StatefulService();
        }
    }
cs

 

2명의 사용자가 10000원 20000원을 요청하는 소스를 만들어 돌려주면 userA의 price를 요청했는데 B가 입력한 20000원이 아래와 같이 출력 되게 됩니다.

 

출력값

name : userA price : 10000

name : userB price : 20000

price : 20000

 

10000원이 나오기를 기대하였지만 20000원이 나온 이유는 중간에 userB가 price를 바꿔 버렸기 때문입니다.

 

위 코드에서 price는 공유되는 필드이지만 특정 클라이언트가 값을 변경하고 있어 원하는 결과가 안 나오고 있습니다.

이처럼 공유 필드는 조심하여야 하고 무상태로 설계해야 합니다.

 

 

해결의 위해서는 공유 필드를 사용하지 말고 지역변수를 사용하면 됩니다.

Service에서 코드를 아래와 같이 고치고

1
2
3
4
5
    public int order(String name, int price) {
        System.out.println("name : " + name + " price : " + price);
//        this.price = price; // 요기가 문제 
        return price;
    }
cs

 

다음으로 테스트를 위한 class의 코드를 아래와 같이 수정해 준다면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    @Test
    void statefulServiceSingleton() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 =ac.getBean(StatefulService.class);
        
        //ThreadA
        int userAprice = statefulService1.order("userA"10000);
        //ThreadB
        int userBprice = statefulService1.order("userB"20000);
        
        //사용자A의 주문 금액 조회
//        int price = statefulService1.getPrice();
        System.out.println("price : " + userAprice);
        Assertions.assertThat(userAprice).isEqualTo(10000);
    }
    
cs

 

정상적으로 값이 나오게 됩니다.

출력값

name : userA price : 10000

name : userB price : 20000

price : 10000

 

 

반응형