오늘은 지난 Spring Bean 글에 이어서 Spring의 핵심 원리인 싱글톤 컨테이너에 대해 알아보겠다.
(지난 글 : 2022.12.17 - [언어] - [Java] static)
[Java] static
지난번 글에서 Spring의 Bean이 무엇인지 간략하게 살펴보았는데, 이번에는 Java에서 쓰이는 static이 무엇인지 알아보고 다음번 글을 통해 util을 static method로 만들어 사용해야 할지 아니면 spring bean
corinprodo.tistory.com
아래 링크에 잘 정리되어 있어 참고했다.
[Spring Core] 싱글톤 컨테이너
스프링은 태생이 기업용 온라인 서비스 기술을 지원하기 위해 탄생대부분의 스프링 애플리케이션은 웹 애플리케이션이다. 물론 웹이 아닌 애플리케이션도 얼마든지 개발할 수 있다.웹 애플리
velog.io
먼저 이전 'static'을 주제로 다룬 글에서 Spring bean은 싱글톤으로 동작한다고 했다. 그렇다면 싱글톤 패턴이 무엇인지 먼저 알아보자.
1. 싱글톤 패턴
1. 클래스의 인스턴스가 1개만 생성되는 것을 보장하는 디자인 패턴이다.
2. 객체 인스턴스를 2개 이상 생성하지 못하도록 막는다. ( private 생성자를 이용해 외부에서 new 키워드를 사용하지 못하게 막아야한다.)
Spring과 별개로 이 패턴에 준하도록 Java에서도 static 을 이용해 개발이 가능하다. 하지만 이에 대한 문제가 있어 Spring에서 싱글톤 컨테이너로 bean을 관리해 주는 것이다. 그렇다면 Java의 static을 활용해 구현한 싱글톤 패턴을 살펴보고 문제점을 알아보겠다.
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return this.instance;
}
private SingletonService() { // 생성자를 private로 선언함으로써 외부에서 인스턴스를 생성하지 못하게 막는다.
}
}
위 소스코드는 SingletonService라는 클래스를 싱글톤 패턴에 맞게 구현하였다.
외부에서 생성하지 못하게 생성자를 private로 선언하였고, 해당 클래스를 불러오기 위해서는 static으로 선언된 SingletonService.getInstance() 메소드를 통해서 인스턴스를 불러와야한다.
이를 통해 하나의 인스턴스만 생성하여 활용할 수 있게 된 것이다.
이는 Client의 요청이 들어올 때 마다 객체를 생성하지 않아서 메모리를 절약할 수 있는 장점이 있다. 하지만 문제점도 있으니 문제점을 살펴보자.
문제점
- 구현 코드가 많다.
- static 필드로 해당 인스턴스를 생성하고
- 위 인스턴스를 반환하는 메서드를 만들고
- private 생성자를 만든다.
- 의존 관계상 클라이언트가 구체 클래스에 의존(구체 클래스는 추상 클래스의 반대되는 클래스로 완성되어 있는 것을 사용하는 개념)
- 의존성 역전 원칙을(DIP) 위반한다. → 추상 클래스가 아닌 구체 클래스에 의존하게 되면 유연성이 떨어진다.
- 개방 폐쇄의 원칙을(OCP) 위반한다. →위와 마찬가지로 기능을 추가할 수 있도록 설계되어야 한다는 원칙을 해친다. (구체 클래스가 바뀌면 클라이언트 코드도 바뀌어야하기 때문)
- 테스트가 어렵다.
- 내부 속성을 변경하거나 초기화가 어렵다.
- private 생성자로 자식 클래스를 만들기 어렵다. (상속 받은 클래스를 생성하려면 부모 객체의 기본 생성자가 public으로 접근 가능해야한다.)
- 유연성이 떨어진다.
- 의존성 주입이 어렵다.
- 안티패턴으로 불리기도 한다.
Spring 싱글톤 컨테이너
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다. 여기서 말하는 객체 인스턴스는 bean을 의미한다.
- 스프링 컨테이너는 위 소스코드처럼 클래스를 따로 싱글톤으로 개발하지 않아도 bean을 싱글톤으로 관리한다.
- 이 bean을 생성하고 관리하는 기능을 싱글톤 레지스트리라고 한다.
- 위 Java 싱글톤 클래스로 구현하는 방법에 대한 단점을 해결하고 싱글톤으로 유지할 수 있는 장점이 있다.
- 지저분한 소스코드가 들어가지 않는다. (getinstance() 메소드 생성, private 생성자 생성 등)
- DIP, OCP, 테스트 private 생성자로부터 자유롭게 싱글톤을 사용할 수 있다.
- 인스턴스는 하나만 생성하여 공유하는 것이다.
- 참고로 스프링의 기본 빈 등록방식은 싱글톤이지만, 싱글톤 방식만 지원하는 것은 아니다. 요청할 때마다 새로운 객체를 생성해서 반환하는 것도 가능하다. (prototype scope, 웹 scope 등)
싱글톤 컨테이너 문제점
컨테이너의 문제점은 아니고 싱글톤 패턴 자체의 문제점이다.
전역에서 공유되는 객체이기 때문에 멀티쓰레드 환경에서 동시성 문제가 발생할 수 있다.
힙 메모리 영역에 할당되기 때문에 모든 쓰레드가 사용할 수 있다.
따라서 흔히 얘기하는 stateless로 설계를 해야한다. stateless는 상태가없는 것을 말하는데 내가 이해한 바로는 값이 변화가 없는 상태를 말하는 것 같다.
예를 들어, Component와 같은 bean 내에 상태가 변할 수 있는(stateful) 변수가 있다면 이를 제거해줘야 한다.
또 최대한 공유되지 않는 변수, 지역변수, 파라미터, ThreadLocal 등을 활용하면 된다.
아래는 예시이다.
public class StatefulService {
private int price;
public void order(String name, int price) {
this.price = price;
}
public int getPrice() {
return this.price;
}
}
public class StatefulTest {
@Test
void statefulServiceTest() {
ApplicationContext = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
StatefulService2 = ac.getBean("statefulService", StatefulService.class);
//ThreadA : A사용자 10000원 주문
statefulService1.order("userA",10000);
//ThreadB : B사용자 20000원 주문
statefulService2.order("userB",20000);
//ThreadA : 사용자 A주문 금액 조회
int price = statefulService1.getPrice();
//ThreadA : 사용자 A는 10000원 기대했지만, 기대와 다르게 20000원 출력
Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
}
private int로 선언한 price 변수의 값이 변하는 것을 확인할 수 있다. 그럼 만약 모든 요청, 쓰레드에 대한 개수를 파악할 목적으로 component를 하나 만든다고 가정할 때 해당 component에 멤버 변수를 하나 만들어 값을 1씩 증가시키고 싶다면 굳이 static을 사용할 필요는 없는 건가? 한 번 알아봐야겠다.
결론
스프링 컨테이너에서 관리하는 자바 객체 (bean)은 싱글톤으로 관리된다.
이는 하나의 인스턴스를 사용하는 것이기 때문에 모든 요청에 대해서 새롭게 instance를 생성하지 않는다.
다만! 동시성 문제가 발생할 수 있으니 꼭 stateless 한 설계 방식을 사용할 것!!!
'백엔드' 카테고리의 다른 글
[Spring] static method VS bean (0) | 2022.12.31 |
---|---|
[Spring] Bean (0) | 2022.12.14 |
[Spring] @Transactional (2) | 2022.11.21 |