싱글톤 방식의 주의점
싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서
공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에, 싱글톤 객체는
상태를 유지하게 설계 하면 안된다.
무상태로 설계해야한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야 한다.
- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, 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;
}
}
StatefulService 클래스를 하나 생성하고 이름과, 가격을 출력하는 함수와
가격을 반환하는 함수를 만들었다.
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);
statefulService1.order("userA",10000);
statefulService2.order("userB",20000);
// 2명의 고객의 요청
int price = statefulService1.getPrice();
System.out.println("price = " + price);
Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
static class TestConfig{
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
위의 클래스를 테스트 해보았다.
TestConfig 설정정보를 만들고
빈을 정의해준 뒤
스프링 컨테이너를 생성하여
두명의 고객의 정보를 넣어주고 이때 가격을 확인한다.
A유저는 10000원을 결제해야하는데 20000원이 결제되는 상황이 발생할 코드이다.
StatefulService의 price 필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경한다.
공유필드는 항상 조심해야한다. 스프링 빈은 무상태로 설계되어야한다.
package hello.core.singleton;
public class StatefulService {
public int order(String name, int price){
System.out.println("name = " + name + " price = " + price);
return price;
}
}
이 코드처럼 공유되는 price필드를 지역변수로 사용하는 방법이 있다.
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);
int userAPrice = statefulService1.order("userA",10000);
int userBPrice = statefulService2.order("userB",20000);
// 2명의 고객의 요청
System.out.println("price = " + userAPrice);
}
static class TestConfig{
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
이렇게 코드를 짜면
기대했던 값을 확인 할 수 있었다.
실무에서 이런 경우들을 종종 볼 수 있다고한다..
그때는 잡기가 어렵고 해결하기 어려운 문제들이 터진다고한다!
'웹프로그래밍 > Spring 핵심 원리' 카테고리의 다른 글
31. @Configuration과 바이트코드 조작의 마법 (0) | 2021.07.30 |
---|---|
30. @Configuration과 싱글톤 (0) | 2021.07.30 |
28. 싱글톤 컨테이너 (0) | 2021.07.23 |
27. 싱글톤 패턴 (0) | 2021.07.22 |
26. 웹 애플리케이션과 싱글톤 (0) | 2021.07.22 |