개발/Spring

(스프링 기본) 15 - 싱글톤 방식의 주의점 (공유필드)

개발하는 인사담당자 2024. 10. 24. 11:21

싱글톤 객체는 여러 클라이언트가 하나의 객체 인스턴스를 공유하기 때문에

객체는 상태를 유지(stateful)하게 설계하면 안되고 무상태(stateless)로 설계해야 한다.

 

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

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

- 가급적 읽기만 가능해야 함

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

- 스프링 빈의 필드에 공유값을 설정하면 큰 장애 발생할 수 있음. (주의!!★)

 

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("userB", 20000);

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

    }

    static class TestConfig {

        @Bean
        public StateFulService stateFulService() {
            return new StateFulService();
        }

    }

}

 

여기서 둘 다 모두 같은 객체를 바라보고 있기 때문에(싱글톤)

order 처리를 한 후 getPrice()를 하면 A 사용자가 주문 금액을 조회해도 20000원이 나온다. 

 

StatefulService 의 price 필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경한다. (문제발생)

 

public class StateFulService {
//    private int price; //상태를 유지하는 필드
    public int order(String name, int price) {
        System.out.println("name = " + name + "price = " + price);
//        this.price = price; //여기가 문제!
        return price;

    }
    @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("userB", 20000);

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

//        Assertions.assertThat(stateFulService1.getPrice()).isEqualTo(20000);

    }

 

따라서 이를 int 값으로 반환값으로 아예 돌려주게 만들고

원래 코드에서 직접 값을 출력하게 한다.  

 

출처 : 김영한 스프링 핵심 원리 - 기본편

https://www.inflearn.com/course/%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

 

스프링 핵심 원리 - 기본편 강의 | 김영한 - 인프런

김영한 | 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보

www.inflearn.com