개발자 미니민의 개발스터디

[Spring 핵심원리 기본편] 웹 애플리케이션과 싱글톤2

by mini_min


싱글톤 방식의 주의점

싱글톤 객체는 상태를 유지하게 설계하면 안된다. 무상태로 설계해야한다!
- 특정 클라이언트에 의존적인 필드가 있으면 안된다. 
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야한다.
- 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야한다.

package hello.core.singleton;

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;
    }

}

 

price 필드는 공유되는 필드로, 특정 클라이언트가 값을 변경하고 있다. 그래서 마지막 2만원이라는 결과가 나온다. 

공유 필드는 특히나 더 조심! 주의해야한다. 스프링 빈은 항상 무상태로 설계하자.

package hello.core.singleton;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

import static org.junit.jupiter.api.Assertions.*;

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 사용자가 만원 주문 / B가 2만원 주문
        statefulService1.order("userA", 10000);
        statefulService1.order("userB", 20000);


        //ThreadA: 사용자A가 주문 금액 조회
        int price = statefulService1.getPrice();
        //의도는 1만원이 나와야하는데, 2만원이 나온다.
        //같은 인스턴스를 쓰기 때문에 마지막 2만원이 나올 수 밖에 ...
        System.out.printf("price = " + price);

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

    static class TestConfig{

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


}

 

@Configuration 과 싱글톤

스프링 컨테이너는 싱글톤 레지스트리다. 따라서 스프링 빈이 싱글톤이 되도록 유지해야한다. 
* 비밀은 @Configuration !

 

스프링이 CGLIB 라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한다. 그 임의의 다른 클래스가 바로 싱글톤을 보장해준다. 

-> 내가 만든 클래스가 스프링 컨테이너에 등록이 되어 있다면? -> 찾아서 반환!
-> 만약 없다면, 기존 로직을 호출해서 생성 후 스프링 컨테이너에 등록하고 반환!

 

만약 @Configuration 을 빼고 @Bean 만 적용한다면?
= 싱글톤 깨짐 ㅠㅠ 순수한 자바 코드 실행이 된다...ㅎ

 

정리

@Bean 만 사용해도 스프링 빈으로 등록되지만, 싱글톤은 보장하지 않는다. 메서드를 직접 호출하기 때문에 싱글톤을 보장하지 않는다. 스프링 설정 정보는 @Configuration 을 사용하도록 하자.

 

 

 

블로그의 정보

개발자 미니민의 개발로그

mini_min

활동하기