꼬물꼬물
싱글톤 컨테이너 [싱글톤 컨테이너] 본문
싱글톤 컨테이너
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하며, 객체 인스턴스를 싱글톤으로 관리한다.
스프링 빈 == 싱글톤으로 관리되는 빈
"싱글톤 컨테이너"
- 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다
- 컨테이너의 생성 과정을 보면 컨테이너는 객체를 하나만 생성해 관리한다.
- 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 싱글톤 객체를 생성, 관리하는 기능을 싱글톤 레지스트리라 한다.
- 스프링 컨테이너의 기능 덕분에 싱글턴 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수 있다.
- 싱글톤 패턴을 위한 코드 단축
- DIP, OCP, 테스트, private 생성자로부터 자유롭게 싱글톤을 사용한다.
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//1. 조회: 호출할 때마다 객체 생성
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
System.out.println("memberService1 = "+memberService1);
System.out.println("memberService2 = "+memberService2);
assertThat(memberService1).isSameAs(memberService2);
}
// 결과
memberService1 = hello.core.member.MemberServiceImpl@35e5d0e5
memberService2 = hello.core.member.MemberServiceImpl@35e5d0e5
- 스프링 컨테이너를 사용하면 동일한 객체를 반환한다.
- 스프링 컨테이너 사용으로 고객의 요청이 들어올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해 효율적으로 재사용할 수 있다.
- + 스프링 기본 빈 등록 방식은 싱글톤이지만, 이 방식만 지원하는 것은 아니다. 요청마다 새로운 객체를 생성해 반환하는 기능도 제공 -> 빈 스코프
싱글톤 방식의 주의점
- 싱글톤 패턴 / 스프링의 싱글톤 컨테이너 모두 객체 인스턴스를 하나만 생성해 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체의 상태를 유지(stateful)하게 설계하면 안된다.
- 무상태(stateless)로 설계해야 한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야 한다. == 수정이 없어야 한다.
- 필드 대신 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
- 스프링 빈의 필드에 공유 값을 설정하면 큰 장애가 발생할 수 있다!
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;
}
}
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); // 공유 구역에 있기 때문에 price가 업데이트 된다.
// ThreadA: 사용자A 주문 금액 조회
int price = statefulService1.getPrice();
System.out.println("price = "+price);
assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
@Configuration
static class TestConfig{
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
- ThreadA가 사용자A 코드를 호출하고 ThreadB가 사용자B 코드를 호출한다.
- StatefulService의 price 필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경한다.
- 공유 필드는 항상 조심해야 한다! 스프링 빈은 항상 무상태(Stateless)로 설계하자
해결: 로컬 변수 사용하기
package hello.core.singleton;
public class StatefulService {
public int order(String name, int price){
System.out.println("name = " + name + ", price = " + price);
return price;
}
}
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원 주문
int userAPrice = statefulService1.order("userA", 10000);
// ThreadB: B사용자가 20000원 주문
int userB = statefulService2.order("userB", 20000);// 공유 구역에 있기 때문에 price가 업데이트 된다.
// ThreadA: 사용자A 주문 금액 조회
// int price = statefulService1.getPrice();
System.out.println("price = "+userAPrice);
// assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
@Configuration
static class TestConfig{
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
'스터디 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
컴포넌트 스캔 [의존관계 자동 주입, 탐색 위치와 기본 스캔 대상] (0) | 2022.09.19 |
---|---|
싱글톤 컨테이너 [@Configuration] (0) | 2022.09.18 |
싱글톤 컨테이너 [싱글톤 패턴] (0) | 2022.09.17 |
스프링 컨테이너와 스프링 빈 [스프링 컨테이너 생성과 스프링 빈 조회] (0) | 2022.09.14 |
스프링 핵심 원리 이해2 - 객체 지향 원리 적용 [IoC, DI 그리고 컨테이너, 스프링 전환] (0) | 2022.09.12 |