package hello.core.singleton;
import hello.core.AppConfig;
import hello.core.member.MemberService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class SingletonTest {
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainer(){
AppConfig appConfig = new AppConfig();
MemberService memberService1 = appConfig.memberService();
MemberService memberService2 = appConfig.memberService();
System.out.println("memberService1 = " + memberService1); //다르게 생성됨
System.out.println("memberService2 = " + memberService2);
//MemberService1 != MemberService2
Assertions.assertThat(memberService1).isNotSameAs(memberService2);
}
}
package hello.core.singleton;
public class SingletonService {
//private static -> 딱 하나만 존재
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance(){
return instance;
}
//3. 생성자를 private으로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
private SingletonService() {
}
public void logic() {
System.out.println("싱글톤 객체 로직 호출");
}
}
@Test
@DisplayName("싱글톤 패턴을 적용 객체 사용")
void SingletonService(){
new SingletonService();
}
이렇게 호출할 경우에는, 아래와 같은 에러 발생

@Test
@DisplayName("싱글톤 패턴을 적용 객체 사용")
void SingletonService(){
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
System.out.println("singletonService1 = " + singletonService1);
System.out.println("singletonService2 = " + singletonService2);
}
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer(){
// AppConfig appConfig = new AppConfig();
ApplicationContext 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);
//MemberService1 != MemberService2
assertThat(memberService1).isSameAs(memberService2);
}

싱글톤 방식은 여러 클라이언트가 공유하기 때문에, 상태 유지하게 설계를 하면 안된다
무상태로 설계해야한다
- 특정 클라이언트에 의존적인 필드 X
@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); //20000 원이 나옴
//인스턴스가 같기 때문에 그 객체에 있는 price 가 값이 잘못나온 것
Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
// 아래 코드는 위의 코드를 무상태로 설계했을 때의 모습이다
public int order(String name, int price){
System.out.println("name = " + name + "price = "+ price);
this.price = price;
return price;
}
- TreadA가 사용자 A 코드 호출, ThreadB가 사용자 B 호출
- StatefulService` 의 `price` 필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경한다.
- 스프링은 항상 무상태로 설계해야한다