티스토리 뷰

Spring

[Spring] Singleton(싱글톤)

MAENCO 2021. 8. 13. 14:05
반응형

스프링으로 주로 개발되는 웹 애플리케이션의 경우

대부분 여러 고객이 동시 요청을 하는 상황이 발생되게 된다

 

이때 요청마다 새로운 객체를 생성한다면 수많은 객체를 만들어야 할 것이고

트래픽이 많아지게 되면 결과적으로는 굉장히 비효율적인 구조가 될 것이다

이렇게 객체를 새로 만드는 것보다 이미 존재하고 있는 객체를 조회하는 것이

메모리적으로는 비교할 수 없을 정도로 효율적이라고 한다

 

이로써 객체를 새로 만들지 않고 공유하게 해주는 싱글톤 패턴을 사용하면 문제점을 해결할 수 있다

 

Singleton Pattern(싱글톤 패턴)

싱글톤 패턴은 클래스의 객체를 딱 1개만 생성하여 이를 공유(조회)할 수 있게 해준다

public class SingletonService {
	
    // static 영역에 미리 객체를 1개 생성한다
    private static final SingletonService instance = new SingletonService();
	
    // public 메서드를 통해 객체가 필요하다면 이 static 메서드를 통해서만 조회 하도록 허용한다
    public static SingletonService getInstance() {
        return instance;
    }
    
    // 생성자를 private로 선언하여 외부에서 접근하는 것을 막는다
    private SingletonService() {
    }
}

이처럼 static에 미리 객체를 선언하여 private으로 접근을 막고, pulbic 메서드를 통해서만 조회할 수 있게 한다면

클라이언트의 요청마다 이미 만들어진 동일한 객체를 공유할 수 있다 이로써 객체를 새로 생성하지 않을 수 있게 되었다

(싱글톤 패턴을 구현하는 방법은 여러 가지가 있다고 하는데, 객체를 미리 생성해두는 것이 가장 단순하고 안전한 방법이라고 한다)

 

하지만 이러한 싱글톤 패턴도 문제점이 존재한다

1. 싱글톤 패턴을 구현하는 코드를 작성하기 번거롭다

2. 의존관계상 클라이언트가 구체 클래스를 의존하여야 한다 이는 DIP를 위반한다

3. 구체 클래스를 의존하여 OCP 원칙을 지키기 어렵다

4. 테스트를 하기 어렵다

5. 내부의 속성을 변경하거나 초기화하기 어렵다

6. private 생성자로 인하여 자식 클래스를 만들기 어렵다

 

이러한 문제점들을 보완해주는 것이 바로 Singleton Container이다

 

Singleton Container (Spring Container)

스프링 컨테이너가 싱글톤 패턴으로 빈들은 관리하기 때문에 싱글톤 컨테이너라고도 불린다

이렇게 스프링 컨테이너가 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라고 한다

스프링 컨테이너 덕분에 지저분한 코드, DIP, OCP, 테스트 그리고 private 생성자로부터 자유롭게 싱글톤을 유지할 수 있게 되었다

그렇다면 스프링 컨테이너는 자바 코드를 어떻게 읽어서 빈들을 싱글톤으로 관리하고 유지할까?

 

Configuration

스프링 컨테이너가 싱글톤 방식을 사용하는 메커니즘의 핵심은 Configuration에 있다

설정 파일을 만들기 위하여 클래스에 이 어노테이션을 붙이는데

@Configuration 어노테이션이 붙은 클래스를 출력해보면

bean = class hello.core.order.AppConfig$$EnhancerBySpringCGLIB$$c8a6b8a6

위와 같이 순수한 클래스명 뒤에 CGLIB이라는 게 있는 걸 확인할 수 있다

이는 스프링 컨테이너가 CGLIB이라는 바이트코드 조작 라이브러리를 사용하여

기존의 AppConfig를 상속받은 임의의 클래스를 생성하여 빈으로 등록하고 제공함으로써

단순한 자바 코드더라도 싱글톤을 유지시킬 수 있는 것이다

 

Stateless(무상태 설계)

싱글톤 패턴이든 싱글톤 컨테이너든 핵심은 같은 객체를 하나만 생성해서 공유한다는 것이다

이때 객체의 상태가 stateful(유지)하게 설계되면 수많은 문제에 씨앗이 된다

 

먼저 문제점부터 살펴보자

public class StatefulService {
	public int price;
    
    public int order(String name, int price) {
   		this.price;
    }
}

이렇게 상태가 유지되는 상태에서 테스트를 돌려보면

@Test
void statefulServiceSingleton() {
     ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

     StatefulService statefulService1 = ac.getBean(StatefulService.class);
     StatefulService statefulService2 = ac.getBean(StatefulService.class);

     int userAPrice = statefulService1.order("userA", 10000);
     int userBPrice = statefulService2.order("userB", 20000);
        
     System.out.println("test result = " + userAPrice);
 }

이렇게 테스트를 돌려보면 기존에 userAPrice에 입력하였던 10000이 아니라

스레드의 논리에 따라 20000이 나오게 된다

 

만약 이것이 결제 과정이라고 생각한다면 A라는 클라이언트는 자신의 주문한 10,000원이 아니라

다른 사람이 주문한 금액 20,000원을 결제해야 하는 상황이 발생할 수 있다

 

이러한 상황을 방지하기 위하여 객체의 상태를 반드시 Stateless(무상태)로 만들어야 한다

public class StatefulService {
	public int price;
    
    public int order(String name, int price) {
   	return price;
    }
}

위의 코드와 같이 파라미터로 값으로 받아오게 될 경우 입력되는 값을 반환하기 때문에 무상태를 유지할 수 있다

 

핵심정리

정리해보자면 스프링 컨테이너가 싱글톤 방식을 사용하는 이유는

스프링을 사용하여 주로 개발되는 웹 애플리케이션의 특성상

수많은 클라이언트가 동시에 요청하는 상황이 빈번하게 발생하고

 

이때 새로운 객체를 생성하여 제공하는 것은 매우 비효율적이기 때문에

하나의 객체만을 공유해서 사용할 수 있도록 보장하는 싱글톤 방식을 사용한다

 

더보기

개인 학습을 위해 작성되는 글입니다.

제가 잘못 알고 있는 점에 대한 지적 / 더 나은 방향에 대한 댓글을 환영합니다.

 

참조 링크:

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard
https://castleone.tistory.com/2

 

반응형
댓글