public class SingletonService {
// 1. static 영역에 객체를 딱 1개만 생성해둔다.
private static final SingletonService instance = new SingletonService();
// 2. public으로 열어서 객체 인스턴스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용한다.
public static SingletonService getInstance() {
return instance;
}
// 3. 생성자를 private으로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
private SingletonService() {
}
public ovid logic() {
System.out.println("싱글톤 객체 로직 호출");
}
}
instance
를 미리 하나 생성해서 올려둔다.getInstance()
메서드를 통해서만 조회할 수 있다. 이 메서드를 호출하면 항상 같은 인스턴스를 반환한다.참고 : 싱글톤 패턴을 구현하는 방법은 여러가지가 있다. 여기서는 객체를 미리 생성해두는 가장 단순하고 안전한 방법을 선택했다.
싱글톤 패턴을 적용하면 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 사용할 수 있다. 하지만 싱글톤 패턴은 다음과 같은 수 많은 문제점들을 가지고 있다.
코드 자체가 많이 들어간다.
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스 싱글톤(1개만 생성)으로 관리한다. 지금까지 우리가 학습한 스프링 빈
이 바로 싱글톤으로 관리되는 빈이다.
스프링 컨테이너를 사용하는 테스트 코드
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//1. 조회: 호출 할때마다 같은 객체를 변환
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
//2. 조회 : 호출할 때 마다 같은 객체를 반환
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
// 참조값이 같은것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberSerivce2 = " + memberService2);
//memberService1 == memberServie2
Assertions.assertThat(memberService1).isSameAs(memberService2);
}
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;
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사용자 10000원 주문
statefulService1.order("userA", 10000);
// ThreadB : B사용자 20000원 주문
statefulService2.order("userB", 20000);
// ThreadA : 사용자A 주문 금액 조회
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();
}
}
}
StatefulService
의 price
필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경한다.