스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
(스프링 컨테이너 생성과정에서, 컨테이너는 객체를 하나만 생성해서 관리)
스프링 컨테이너가 싱글톤 컨테이너 역할을 해서 싱글톤 객체를 생성하고 관리한다.
이 기능을 싱글톤 레지스트 라고 한다.
싱글톤 레지스트의 장점은
- 싱글톤 패턴을 위한 지저분한 코드 필요x
- DIP, OCP, 테스트, private 생성자로부터 자유롭게 싱글톤 사용 가능
이전의 코드에서 스프링 컨테이너를 사용하는 부분만 바꾸어 테스트를 진행해보자!
void springContainer(){
// AppConfig appConfig = new AppConfig();
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("memberService2 = " + memberService2);
//memberService1 == memberService2
assertThat(memberService1).isSameAs(memberService2);
}
결과는 역시 동일한 객체이다.
memberService1 = hello.core.member.MemberServiceImpl@3d9f6567
memberService2 = hello.core.member.MemberServiceImpl@3d9f6567
※ 스프링의 기본 빈 등록 방식은 싱글톤이지만, 싱글톤 뿐 만 아니라 요청할 때 마다 새로운 객체를 생성해서 반환하는 기능도 제공한다.
그러나 싱글톤 방식에서 주의해야할 점이 있다.
객체 인스턴스를 하나만 생성해서 공유하기 때문에 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유한다.
따라서, 싱글톤 객체는 상태를 유지(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;
}
}
그 후, 테스트에서 아래와 같이 2명의 사용자를 호출해보자.
@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 주문 금액 조회, 같은 객체를 사용하므로 20000원이 나옴
int price = statefulService1.getPrice();
System.out.println("price = " + price);
Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
정상적이라면, userA와 userB는 다른 주문이므로 결과는 10000원이 나와야 할 것이다.
그러나 싱글톤에서는 같은 객체를 사용하므로 20000원이 나온다.
위 클래스의 price 필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경한 것이다.
이렇게 공유필드는 항상 조심해서 설계하고 무상태로 설계해야 한다.
위 예시에서 문제를 해결하는 방법은 위에 나와있듯이,
자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용하면 된다.
package hello.core.singleton;
public class StatefulService {
// private int price; // 상태를 유지하는 필드 10000 -> 20000
public int order(String name, int price){
System.out.println("name = " + name + " price = " + price);
// this.price = price; // 문제 부분
return price;
}
}
'Tech > Spring' 카테고리의 다른 글
[스프링 핵심 원리] 다양한 의존관계 주입 방법 (0) | 2022.01.17 |
---|---|
[스프링 핵심 원리] 빈 중복 등록과 충돌 (0) | 2022.01.15 |
[스프링 핵심 원리] 웹 애플리케이션과 싱글톤 (0) | 2022.01.11 |
[스프링 핵심 원리] 스프링 빈 설정 메타 정보 - BeanDefinition (0) | 2022.01.10 |
[스프링 핵심 원리] 스프링 컨테이너 기초 (0) | 2022.01.05 |