❗ 싱글톤 방식의 주의점
싱글톤 패턴이든 , 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하는 설계를 지양합니다.
👉 무상태(stateless)로 설계해야 함!
싱글톤 패턴을 사용하는 방식에는 몇 가지 주의해야 할 점이 있습니다. 이러한 주의점을 알고 사용함으로써 문제를 예방하고 싱글톤의 장점을 최대한 활용할 수 있습니다. 주요한 주의점은 다음과 같습니다:
- 멀티스레딩 환경에서의 동시성 문제: 싱글톤은 전역 상태를 유지하므로, 멀티스레드 환경에서 여러 스레드가 동시에 접근할 경우 동기화 문제가 발생할 수 있습니다. 따라서 동시성 문제를 방지하기 위해 스레드 안전(Thread-Safe)한 싱글톤 구현 방식을 선택하거나 동기화 메커니즘을 적절히 사용해야 합니다.
- 의존성 관리의 어려움: 특정 클라이언트에 의존적인 필드가 있으면 안됩니다. 가급적 값을 수정하면 안되고, 읽기 기능만 가능하도록 해야합니다. 싱글톤은 전역적인 상태를 가지고 있기 때문에 의존성이 복잡한 경우 다른 객체에 대한 의존성을 주입하는 것이 어려울 수 있습니다.
❗ 스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있습니다.
ㅡㅡㅡㅡㅡㅡㅡㅡ주문 정보를 입력받아 출력하는 파일ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
public class StateFulService {
private int price; //상태를 유지하는 필드 10000->20000으로 바꿔치기 됨
public void order(String name, int price) {
System.out.println("name = " + name + " " +"price = " + price);
this.price = price;
}
public int getPrice() {
return price;
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡTest 코드가 작성된 파일ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
class StateFulServiceTest {
//해당 스프링 컨테이너는 하단에 생성된 TestConfig안의 1개의 Bean만 생성해서 사용
@Test
void statefulServiceSingleton() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StateFulService stateFulService1 = ac.getBean(StateFulService.class);
StateFulService stateFulService2 = ac.getBean(StateFulService.class);
//ThreadA : userA사용자는 10000원어치 주문
stateFulService1.order("userA", 10000);
//ThreadB : B 사용자는 20000원을 주문
stateFulService2.order("userB", 20000);
//ThreadA : 사용자 A가 주문 금액을 조회하고 싶을때 사용하는 메서드 작성
//A가 주문을 하고, 금액을 조회하는 사이에 B의 주문이 들어온 상황
int price = stateFulService1.getPrice();
System.out.println("price = " + price);
//출력 price=20000(중간에 B의 주문이 들어오게 되며 price의 값이 변경)
//A사용자는 10000원을 주문했는데, 중간의 가격 변경으로 price가 20000으로 변경되었는데도 Test는 통과됨
assertThat(stateFulService1.getPrice()).isEqualTo(20000);
}
ㅡㅡㅡ해당코드에서 임시로 사용할 Bean 생성ㅡㅡㅡ
static class TestConfig {
@Bean
public StateFulService stateFulService() {
return new StateFulService();
}
}
}
ThreadA가 사용자 A의 코드를 출력하고, ThreadB가 사용자 B의 코드를 출력한다고 가정하에 StateFulService의 price 필드는 공유되는 필드인데, 특정 클라이언트가 중간에 값을 변경하는 상황이 발생하게 됩니다.
이 때, 공유된 필드값을 같이 사용하게되면서 사용자 A의 주문 금액은 10000원이 되어야 하는데, 20000원이라는 결과가 나오게 되었습니다. 공유필드의 사용은 주의해서 사용해야 하며 스프링 빈은 항상 무상태(stateless)로 설계해야 합니다.
👉 @Configuration과 싱글톤의 관계
@Configuration은 스프링 프레임워크에서 사용되는 어노테이션으로, 스프링 빈(Bean) 구성 클래스를 나타냅니다. @Configuration 어노테이션이 지정된 클래스는 스프링 컨테이너에서 빈으로 인식되고, 해당 클래스 내의 @Bean 어노테이션이 지정된 메서드들이 스프링 빈으로 등록됩니다.
@Configuration을 사용하는 이유는 다음과 같습니다:
- 스프링 빈 등록: @Configuration 어노테이션이 지정된 클래스에서 @Bean 어노테이션이 지정된 메서드들은 스프링 컨테이너에 의해 관리되는 빈으로 등록됩니다. 이를 통해 객체의 인스턴스화, 의존성 주입, 라이프사이클 관리 등을 스프링이 담당하게 됩니다.
- 컨테이너 설정: @Configuration 어노테이션이 지정된 클래스는 스프링 컨테이너의 설정 파일 역할을 수행합니다. 스프링은 @Configuration 어노테이션이 지정된 클래스를 스캔하여 컨테이너의 설정 정보로 활용하며, 이를 통해 스프링 빈들의 관계와 동작 방식을 구성할 수 있습니다.
@Configuration 어노테이션을 사용하여 스프링 빈을 등록할 때 주의해야 할 점은, 기본적으로 스프링은 등록된 빈들을 싱글톤(Singleton)으로 관리한다는 것입니다.
즉, @Configuration 어노테이션이 지정된 클래스 내의 @Bean 어노테이션이 지정된 메서드들이 반환하는 객체들은 스프링 컨테이너 내에서 단 하나의 인스턴스만 생성되고 공유됩니다.
따라서, @Configuration 어노테이션을 사용하여 빈을 등록하면 해당 빈들은 싱글톤 패턴으로 동작하게 됩니다.
이는 애플리케이션 전체에서 객체를 공유하고 메모리 사용을 최적화하는 장점을 가지지만, 주의해야 할 점도 있습니다. 싱글톤으로 등록된 빈은
멀티스레드 환경에서 동시에 접근할 경우 동기화 문제가 발생할 수 있으므로, 동시성에 대한 주의가 필요합니다.
'[ BACKEND] > Spring' 카테고리의 다른 글
[SPRING] Servlet / HTTP Request , Response (0) | 2023.08.02 |
---|---|
[SPRING] Web Application (0) | 2023.07.31 |
[SPRING] Web Application과 Singleton의 관계 (0) | 2023.07.24 |
[SPRING] Bean 메타 정보 ( Bean Factory & Application Context) (0) | 2023.07.23 |
[SPRING] 스프링 빈 조회 - 동일한 타입이 2개 이상 (0) | 2023.07.22 |