class SingletonTest {
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너의 문제점")
void pureContainer() {
AppConfig appConfig = new AppConfig();
// 1. 조회: 호출할 때 마다 객체를 생성
MemberService memberService1 = appConfig.memberService();
// 2. 조회: 호출할 때 마다 객체를 생성
MemberService memberService2 = appConfig.memberService();
// 3. 참조값이 다른 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
// memberService1 != memberService2
assertThat(memberService1).isNotSameAs(memberService2);
}
}
public class SingletonService {
// static을 통해 단 하나만 등록된다.
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
// 외부에서 사용할 수 없도록 막는다.
private SingletonService() {}
public void logic(){
System.out.println("싱글톤 객체 로직 호출");
}
}
getInstance()
메서드를 통해서만 해당 객체를 가져올 수 있게 한다.class SingletonTest {
@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonServiceTest() {
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
assertThat(singletonService1).isSameAs(singletonService2);
}
}
싱글톤 패턴을 구현하는 방법은 여러가지가 있다.
여기서는 객체를 미리 생성해두는 가장 단순하고 안전한 방법을 선택했다.
싱글톤 패턴을 적용하면 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 사용할 수 있다. 하지만 싱글톤 패턴은 수 많은 문제점들을 가지고 있다.
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤(1개만 생성)으로 관리한다.
지금까지 우리가 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이다.
class SingletonTest {
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// 1. 조회: 호출할 때 마다 객체를 생성
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
// 2. 조회: 호출할 때 마다 객체를 생성
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
// 3. 참조값이 다른 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
// memberService1 != memberService2
assertThat(memberService1).isSameAs(memberService2);
}
}
스프링의 기본 빈 등록 방식은 싱글톤이지만, 싱글톤 방식만 지원하는 것은 아니다. 요청할 때 마다 새로운 객체를 생성해서 반환하는 기능도 제공한다.
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;
}
}
StatusfulService
의 price
필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경한다.