공hannah부
싱글톤 (Singleton) 본문
웹 애플리케이션과 싱글톤
웹 애플리케이션은 보통 여러 고객이 동시에 요청
↓
스프링이 없는 순수한 DI 컨테이너인 AppConfig는 요청을 할 때마다 객체 새로 생성
↓
메모리 낭비
↓
해당 객체가 1개만 생성되고, 공유하도록 설계
↓
싱글톤 패턴
싱글톤 패턴
싱글톤 패턴이란?
- 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
- private 생성자를 사용해 외부에서 new 키워드를 사용하지 못하도록 막아야 한다.
구현
- static 영역에 객체 instance를 미리 하나 생성
- 이 객체 인스턴스가 필요하면 오직 getInstance() 메서드를 통해서만 조회 가능 → 항상 같은 인스턴스 반환
- 딱 한개의 객체 인스턴스만 존재해야 하므로 생성자를 private으로 막음
package hello.core.singleton;
public class SingletonService {
private static final SingletonService instance = new SingletonService(); //자기자신을 생성하여 instance에 넣는다.
public static SingletonService getInstance() { //조회할 때 쓴다.
return instance;
}
private SingletonService() {
}
public void logic() {
System.out.println("싱글톤 객체 로직 호출");
}
}
Test 코드로 확인
- 스프링이 없는 순수한 DI 컨테이너에서는 각각의 참조값이 다름을 확인할 수 있다.
- 싱글톤 패턴을 적용한 객체를 사용하면 참조값이 같음을 확인할 수 있다.
- 즉, 싱글톤 패턴을 적용하면 고객의 요청이 올 때마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 사용할 수 있다!
package hello.core.singleton;
import hello.core.member.MemberService;
import hello.core.order.AppConfig;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class SingletonTest {
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainer() {
AppConfig appConfig = new AppConfig();
//1. 조회 = 호출할 때 마다 객체를 생성
MemberService memberService1 = appConfig.memberService();
//2. 조회 = 호출할 때 마다 객체를 생성
MemberService memberService2 = appConfig.memberService();
//2개의 객체의 참조값이 다른 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
Assertions.assertThat(memberService1).isNotSameAs(memberService2);
}
@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonServiceTest()
{
//private으로 생성자를 막아두었기 때문에 new SingletonService()으로 객체를 생성한다면 컴파일 오류가 난다.
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
//같은 값을 반환함을 확인
System.out.println("singletonService1 = " + singletonService1);
System.out.println("singletonService2 = " + singletonService2);
Assertions.assertThat(singletonService1).isSameAs(singletonService2);
}
}
싱글톤 패턴의 문제점
- 구현하는 코드 多
- 의존관계상 클라이언트가 구체 클래스에 의존 → DIP 위반, OCP 원칙을 위반할 가능성 ↑
- 테스트 어려움
- 내부 속성 변경 및 초기화 어려움
- Private 생성자로 자식 클래스 만들기 어려움
- 유연성 ↓
→ 스프링 컨테이너는 다 해결!!
스프링 컨테이너
스프링 컨테이너란?
- 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다.
- 컨테이너 내에서 특정 클래스에 대해 @Bean이 정의되면, 그 클래스에 대해 딱 한 개의 인스턴스를 생성하고, @Bean이 호출될 때마다 생성된 공유 인스턴스 리턴한다.
- 싱글톤 객체를 생성하고 관리하는 기능 → 싱글톤 레지스트리
장점
- 싱글톤 패턴의 단점을 해결하면서 객체를 싱글톤으로 관리 가능
- 코드 간단
- DIP, OCP, 테스트, private 생성자로부터 자유롭게 싱글톤 사용 가능
싱글톤 방식의 주의점
싱글톤 객체는 유지(stateful)가 아닌 무상태(stateless)로 설계해야 한다!
- 특정 클라이언트에 의존적인 필드가 있으면 X
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 X
- 가급적 읽기만 가능해야 한다
- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, 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;
}
}
Test 코드
@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 사용자 10000원 주문
int userBPrice = statefulService2.order("userB", 20000);
//ThreadA : 사용자A 주문 금액 조회
System.out.println("price = " + userAPrice);
//ThreadB의 값인 20000원이 나온다. -> statefulService 1 이든, 2이든 다 같은 객체를 참조하고 있는 싱글톤이기 때문
}
해결 방법
- 전역변수로 변경하기!
public int order(String name, int price) {
System.out.println("name = " + name + "price = " + price);
return price;
}
자바 싱글톤 vs 스프링 싱글톤
자바 싱글톤: 클래스가 ClassLoader에 의해 단 한번만 인스턴스화 하는 것을 이용하여 구현 됨
- 생성자를 private 으로 선언: 외부에서 클래스의 오브젝트 생성 X
- 참조는 static 으로 정의: 어느 영역에서든 접근 가능
스프링 싱글톤: 스프링 컨테이너에 의해 구현 됨
- 컨테이너 내에서 특정 클래스에 대해 @Bean이 정의되면, 스프링 컨테이너는 그 클래스에 대해 한 개의 인스턴스를 만듦
- 이 공유 인스턴스는 설정 정보에 의해 관리되고, bean이 호출 될 때마다 스프링은 생성된 공유 인스턴스를 리턴 시킴
'공부 > 백엔드' 카테고리의 다른 글
인증 방식의 종류 & JWT (0) | 2023.11.02 |
---|---|
Spring Security (0) | 2023.11.02 |
자바 ORM 표준 JPA 프로그래밍 CH08 (프록시와 연관관계 관리) (0) | 2023.07.17 |
자바 ORM 표준 JPA 프로그래밍 CH07 (고급매핑) (0) | 2023.07.10 |
자바 ORM 표준 JPA 프로그래밍 CH06 (다양한 연관관계 매핑) (0) | 2023.07.03 |