싱글톤 컨테이너를 알기 전에 싱글톤 디자인 패턴을 알면 더 이해가 쉽다.
싱글톤(Singleton) 패턴이란 객체의 인스턴스가 오직 1개만 존재하는 패턴이다.
아래와 같이 생성자를 private로 지정하여 외부에서 객체를 생성할 수 없게 막고 static으로 애플리케이션이 생성될 때 딱 1개를 만들어주고 외부에는 getInstance() 통해 객체 인스턴스에 접근할 수 있게 한다.
public class SingletonService {
//애플리케이션 실행시점에 객체를 한개 생성하고 가지고있는다
private static final SingletonService instance = new SingletonService();
//객체를 사용할 때 처음 만든 객체1개를 갖고온다.
public static SingletonService getInstance(){
return instance;
}
//private 으로 생성자를 지정하면 외부에서 새로운 객체를 만들 수 없다.
private SingletonService(){}
public void login(){
System.out.println("싱글톤 객체 로직 호출");
}
}
싱글톤 패턴을 직접 추가하게 되면 코드의추가, DIP, OCP 위반을 해결하지만 스프링은 싱글톤 컨테이너를 사용하면서 문제점을 해결해준다.
스프링 컨테이너는 싱글톤 패턴의 코드를 적지 않아도 객체 인스턴스를 싱글톤으로 관리한다.
스프링이 싱글톤 패턴의 단점(DIP,OCP위반, 지저분한 코드추가)을 해결하면서 객체를 싱글톤으로 유지한다.
스프링의 기본 전략은 싱글톤 패턴을 전략으로 한다. 요청(Request)를 받을 때 마다 객체가 새로 생성된다.
각 객체의 참조값이 다른 걸 보아서 싱글톤 패턴임을 알 수 있다.
public class SingletonTest {
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
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);
}
}
스프링이 싱글톤 전략으로 객체를 관리하고 있어서 클라이언트 1명이 1초동안 1000개의 요청을 해도 1개의 객체로 공유하고 사용되기 때문에 트래픽 과부하를 막을 수 있다.
여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체의 상태를 유지(stateful)하게 설계하면 안된다!!
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
를 Bean으로 등록을 하고 2개의 객체를 생성하고 order() 메서드를 실행하면 전혀 다른 결과를 도출한다.@Test
void statefulServiceSingleton(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//Thread A : A사용자가 10000원 주문
statefulService1.order("userA",10000);
//Thread B : B사용자가 20000원 주문
statefulService2.order("userB",20000);
//ThreadA : 사용자 A 주문 금액 조회
int price = statefulService1.getPrice(); //의도는 10000원이 나와야하는데 싱글톤이니까 20000원이 나온다.
System.out.println("price = " + price);
Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
static class TestConfig{
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
즉, 여러 사용자가 공유필드를 사용해서 이런 문제가 생기는 것이다 !
public class StatefulService {
//private int price; // 상태를 유지하는 필드 -- 제거
public int order(String name, int price){
System.out.println("name = " + name + " price = " + price);
return price;
}
}
StatefulService
를 이용하지만 각각의 값이 들어올때 상태를 유지하지 않고 바로 return 해주기 때문에 필드가 상태를 유지하지 않게 되는 무상태(stateless)가 된다.@Test
void statefulServiceSingleton(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);
//ThreadA: A사용자 10000원 주문
int userA = statefulService1.order("userA", 10000);
//ThreadB: B사용자 20000원 주문
int userB = statefulService2.order("userB", 20000);
//ThreadA: 사용자A 주문 금액 조회
System.out.println("price = " + userA);
Assertions.assertThat(userA).isEqualTo(10000);
}
static class TestConfig {
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
코드 출처 : https://d-memory.tistory.com/26