1. 싱글톤 패턴의 필요성
웹 어플리케이션은 고객이 계속 요청을 하는데 현재 코드상으로는 고객이 요청을 할때마다 memberService객체가 매번 만들어지는 문제가 있다.
✅Appconfig코드
//import는 생략했다.
@Configuration //설정정보에 적어주는 어노테이션
public class AppConfig {
@Bean //스프링 컨테이너에 등록이 된다.
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());//생성자 주입
//MemberServiceImpl이 필요로 하는 MemberRepository 의존성을 외부에서 주입
}
@Bean
public MemoryMemberRepository memberRepository() {//구현객체를 반환해주는 역할
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(),discountPolicy());//생성자 주입
}
@Bean
public DiscountPolicy discountPolicy(){
return new RateDiscountPolicy();
}
}
✅테스트 코드(싱글톤 패턴X)
public class SingletonTest {
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainer(){
AppConfig appConfig = new AppConfig();
//1.조회: 호출할 때 마다 객체를 생성
MemberService memberService1 = appConfig.memberService();
//2.조회 : 호출할 때 마다 객체를 생성
MemberService memberService2 = appConfig.memberService();
//참조값이 다른 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
//memberService1과 memverService2는 달라야한다.
//인스턴스를 비교할때는 isSameAs, isNotSameAs를 사용한다.(isEqualTo아님)
Assertions.assertThat(memberService1).isNotSameAs(memberService2);
}
![](https://blog.kakaocdn.net/dn/kR2E0/btsG33ODXmQ/zoS9DOFDwlojGocLBdZEQk/img.png)
테스트 결과, 참조값이 다른 것을 확인할 수 있다.
클라이언트가 memberService를 호출할 때마다 새로운 객체가 만들어진다면, 메모리 낭비가 심할 것이다.
그래서 해당 객체가 하나 생성되고 공유하도록 '싱글톤 패턴'을 쓰면 된다.
2. 싱글톤 패턴
클래스 인스턴스가 1개만 생성되도록 보장하는 디자인 패턴이다.
외부에서 new 키워드로 생성하지 못하도록 한다.
✅싱글톤 패턴 코드
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 void logic() {
System.out.println("싱글톤 객체 로직 호출");
}
}
(※싱글톤 패턴을 만들때, 멀티스레드로 인해 동시에 인스턴스가 만들어지지 않도록 동시성 제어를 해주기도 한다.
이 코드는 단순한 코드 예제임.)
✅싱글톤 패턴 테스트
@Test
@DisplayName("싱글톤 패턴을 활용한 객체 생성")
void singletonServiceTest(){
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
System.out.println("singletonService1 = " + singletonService1);
System.out.println("singletonService2 = " + singletonService2);
Assertions.assertThat(singletonService1).isSameAs(singletonService2);
}
![](https://blog.kakaocdn.net/dn/b3TGdq/btsG5vDEI2U/6VP8VBduss3zftOHXphOO0/img.png)
두 객체의 참조값이 같은 것을 확인할 수 있다.
객체는 하나만 만들어지고 객체를 공유했다는 것을 알 수 있다.
❗주의
- 이런 싱글톤 패턴의 특성때문에 무상태(stateless)로 설계해야한다.
무슨말이냐면, 싱글톤 객체는 상태가 바뀌면 안되고 읽기만 가능하도록 해야한다는 것이다.
값이 변경되면 XX안된다XX
3. stateful 설계 예시(싱글톤 패턴 주의)
싱글톤 패턴은 한 객체를 생성하고 객체를 공유하기때문에 상태가 변경되면 안된다.
상태가 변경되도록 코드를 짠 경우, 상태가 덮어씌워질 수 있다.
예를 들면, A고객의 결제 금액이 B고객의 결제 금액과 바뀔 수 있다.
✅stateful 예시코드
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: 10000원 주문
statefulService1.order("userA", 10000);
statefulService2.order("userB", 20000);
//가격 조회 10000원이어야하는데 userB의 가격으로 덮어씌워짐
int price = statefulService1.getPrice();
System.out.println("price = " + price);
assertThat(userAprice).isEqualTo(10000);
}
static class TestConfig{//임시 설정
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
![](https://blog.kakaocdn.net/dn/bBwQee/btsG3ynVhAm/6K9ppNrU6Iv26gK6RBkER0/img.png)
userA의 price가 10000원으로 나와야하는데, userB의 price로 덮어씌워져서 20000이 됐다.
이 부분을 해결하는 방법이 있다.
✅해결 코드
public class StatefulService {
private int price; //상태를 유지하는 필드
public int order(String name, int price) { //int 반환하도록 바꿔줌
System.out.println("name = " + name + " price = " + price);
return 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: 10000원 주문
int userAprice = statefulService1.order("userA", 10000);
int userBprice = statefulService2.order("userB", 20000);
System.out.println("userAprice = " + userAprice);
assertThat(userAprice).isEqualTo(10000);
}
static class TestConfig{
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
![](https://blog.kakaocdn.net/dn/8t2ZC/btsG3fWCHVl/6Zd5XwYn6PjHoeMYrhbTI0/img.png)
4. 싱글톤 패턴의 문제점
1. 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
2. 클라이언트가 구체 클래스에 의존한다.
싱글톤 패턴에서는 싱글톤 클래스 내부에 인스턴스를 생성하고 관리하는 정적 메소드 getInstance()가 있다.
클라이언트 코드는 getInstance() 메소드를 호출하여 인스턴스를 얻는다.
이 과정에서 클라이언트 코드는 구체 클래스에 대한 직접적인 의존성이 생긴다.
3. 위의 이유로 유연성이 떨어진다.
💡싱글톤 패턴의 문제점을 해결하는 것이 싱글톤 컨테이너다.
5. 싱글톤 컨테이너
- 스프링 컨테이너는 기본적으로 싱글톤 컨테이너로 동작한다.
따라서, 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다.
- 스프링의 싱글톤 컨테이너는 애플리케이션 내에서 빈(bean) 인스턴스를 싱글톤 패턴으로 관리하여, 각각의 빈 정의에 대해 단 하나의 인스턴스만을 생성하고 유지한다.
✅싱글톤 컨테이너 테스트코드
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer(){
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과 memverService2는 같아야한다.
//인스턴스를 비교할때는 isSameAs, isNotSameAs를 사용한다.(isEqualTo아님)
Assertions.assertThat(memberService1).isSameAs(memberService2);
}
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
이 부분에서 스프링 컨테이너가 AppConfig 설정 클래스를 사용하여 초기화된다.
테스트를 돌려보면 memberService1과 memberService2의 참조값이 같다.
![](https://blog.kakaocdn.net/dn/cqA3lq/btsG4id6SuM/U5GZUKrJBaTtzabtiXW3W0/img.png)
❗주의할 점
스프링 설정 정보는 항상 @Configuration 어노테이션을 사용해야 싱글톤을 보장받을 수 있다.
@Bean만 사용하면 스프링 빈으로만 등록된다. (싱글톤 보장 X)
'인프런 김영한 강의 정리 > 스프링 핵심원리 기본편' 카테고리의 다른 글
스프링 핵심 원리 기본편 - 의존관계 자동 주입(2)|옵션처리 (0) | 2024.05.04 |
---|---|
스프링 핵심원리 기본편 - 의존관계 자동 주입(1)|다양한 의존관계 주입방법 (0) | 2024.05.02 |
스프링 핵심원리 기본편 - 컴포넌트 스캔 (0) | 2024.05.01 |
스프링 핵심 원리 기본편 - AppConfig (0) | 2024.04.29 |
[스프링 핵심원리] 객체 지향 프로그래밍(SOLID) (0) | 2024.03.14 |