깊이 있는 Singleton

2020. 5. 31. 23:23개발/Java, Spring

 


Singleton의 개념을 알 수 있다 : 무엇이며 언제, 어떻게 사용하는가?

Singleton의 단점을 알 수 있다 : 왜 안티패턴이라 불리는가?

Spring에서 Singleton의 의미를 알 수 있다 : Spring bean, Single Object, Singleton의 관계


1. Singleton Pattern

Singleton Pattern은 GoF가 소개한 디자인 패턴 중 하나이다. 어떤 클래스를  어플리케이션 내에서 하나의 인스턴스가 존재하도록 강제하는 패턴 이다. 이렇게 하나만 만들어지는 클래스의 오브젝트는 애플리케이션 내에서  전역적으로 접근이 가능 하다.

 

이미지 출처 : https://refactoring.guru/design-patterns/singleton

 

Singleton Pattern 사용 시 주의해야 할 점은  상태를 가진 객체를 Singleton으로 만들면 안된다 는 것이다.

이에 관해 멀티 스레드 환경을 생각해보자. 어플리케이션 내에 단 한 개의 인스턴스가 존재하고, 이를 전역에서 접근할 수 있다면 각기 다른 스레드에서 객체의 상태를 마구잡이로 변경시킬 여지가  있다. 상태가 공유된다는 것은 아주 위험한 상황이다.(그럼 Dao와 같은 Singleton(Bean)들은 DB에 저장할 데이터에 대해 어떻게 갖고 있을까?  메소드 안에서 생성되는 로컬 변수는 새로운 값을 저장할 독립적인 공간이 따로 만들어지기 때문에 안전하다.)

즉, 다시 말해 무상태 객체나 설계상 유일해야하는 시스템 컴포넌트를 Singleton으로 만들어야 한다.

언제 필요할까?

 단일 오브젝트만 존재해야하고, 이를 어플리케이션이 여러 곳에서 공유하는 경우 에 주로 사용한다. 

예를 들어 서버의 각 로직을 담당하는 오브젝트들(controller, service 등)이 클라이언트에서 요청이 들어올 때마다 새로 생성된다고 생각해보자. 클라이언트의 요청이 증가할수록, 로직을 담당하는 오브젝트들의 개수도 기하급수적으로 늘어날 것이다. 이는 시스템에서 큰 부하를 일으키고, 서비스 장애로 이어질 수 있다. 그래서 Spring 서버 환경에서 오브젝트(bean)들을 Singleton으로 생성하는 것이다. (이 때, 사용된 Singleton과 지금 얘기하는 Singleton pattern과는 차이가 있으니 느낌만 이해하고 넘어가자)

사용법

1. 클래스 밖에서는 오브젝트를 생성하지 못하도록  기본 생성자를 private 으로 만든다.

2. 생성된 Singleton Object를 저장할 수 있는  자신과 같은 타입의 static 필드 를 정의한다.

3.  static 팩토리 메소드인 getInstance() 를 만들고 이 메소드가 최초로 호출되는 시점에서 한 번만 오브젝트가 만들어지게 한다.

생성된 오브젝트는 static 필드에 저장된다. 또는 스태틱 필드의 초기값으로 오브젝트를 미리 만들어둘 수도 있다.

4. 한 번 오브젝트가 만들어지고 난 후에는 getInstance() 메소드를 통해 이미 만들어져 스태틱 필드에 저장해둔 오브젝트를 넘겨준다.

2. Singleton은 왜 안티패턴이라 불리는가?

SOLID 원칙의 대부분은 인터페이스 설계와 관련이 되어있다. 의존성을 concrete class(구현 클래스)가 아닌 Interface에 두면, 실제 concrete class의 구현이 변경되어도 이를 사용한 코드는 큰 영향을 받지 않는다. 그렇기 때문에 SOLID원칙(OCP, LSP, ISP, DIP등)을 지키기 위해서는 인터페이스로 설계를 해야한다.

 

하지만 싱글톤을 이용하는 경우 대부분 인터페이스가 아닌 콘크리트 클래스의 객체를 미리 생성해놓고 정적 메소드를 이용하여 사용하게 된다. 이는 여러 SOLID원칙을 위반할 수 있는 가능성을 열어둠과 동시에, 싱글톤을 사용하는 곳과 싱글톤 클래스 사이에 의존성이 생기게 된다. 클래스 사이에 강한 의존성, 즉 높은 결합이 생기게 되면 수정, 단위테스트의 어려움 등 다양한 문제가 발생한다.

1. 객체지향과 맞지 않다.

싱글톤의 사용은  전역상태 를 만들 수 있기 때문에 바람직하지 못하다. 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 지양되어야 할 모델이다. 또한 싱글톤은 private 생성자를 갖고 있기 때문에  상속할 수 없다.  static 필드와 메소드를 사용하기 때문에  다형성같은 객체지향의 특징이 적용되지 않는다.

2. 테스트하기 어렵다.

위에서 단위테스트가 어렵다는 점에 대해서는 설명했다. 싱글톤을 사용하게 되면 단위 테스트 말고도 Mock 테스트도 어려워진다!

 

토비의 스프링 3.1 1장

싱글톤은 만들어지는 방식이 제한적이기 때문에 테스트에서 사용될 때 mock 오브젝트 등으로 대체하기가 힘들다

이펙티브 자바 아이템3

클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워질 수 있다. 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글턴이 아니라면 싱글턴 인스턴스를 가짜 구현(mock object)으로 대체할 수 없기 때문이다.

Mockito는 정적 메서드를 mock할 수 없기 때문에 가짜(mock)를 주입하기 어렵다. 대신 static 메소드를 mocking할 수 있는 PowerMock같은 도구를 사용하면 가능해진다.

3. 서버 환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.

생성자를 private하게 두었어도 reflection을 통해 하나 이상의 오브젝트가 만들어질 수 있다. 또한 여러개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생기기 때문에 싱글톤으로서의 가치가 떨어진다.

3. Spring bean, Single Object, Singleton의 관계

Singleton pattern과 동시에 언급되는 몇 가지 키워드들이 있다. 바로 Single Object와 Bean이다. 이들은 무엇이고 Singleton과 어떤 관계일까?

Single Object

사실 Single Object는 말 그대로  단 하나의 객체 를 뜻한다. 주로 우리는 Singleton Pattern을 통해 Single Object가 생성되기를 기대한다. 하지만 위에서 살펴봤듯, Singleton Pattern은 단일 인스턴스 생성을 보장하지 않는다. Singleton Pattern은 Single Object가 되도록, 즉 말 그대로 메모리 상에 객체가 단 하나만 존재하도록 코드를 구현하였지만 분산된 JVM 혹은 Reflection을 통해 복수개의 Object가 될 여지가 있다. 따라서 Singleton과 Single Object는 엄연히 다르다. 

그럼 Bean은 무엇일까?

Bean은 Spring Framework에서 사용되는 키워드이다. 토비의 스프링은 Bean을 아래와 같이 정의했다.

bean 또는 bean object는 스프링이 IoC 방식으로 관리하는 오브젝트라는 뜻이다. 관리되는 오브젝트라고 부르기도 한다. 주의할 점은 스프링을 사용하는 애플리케이션에서 만들어지는 모든 오브젝트가 다 bean은 아니라는 사실이다. 그 중에서 스프링이 직접 그 생성과 제어를 담당하는 오브젝트만을 bean이라고 부른다.

스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다. 다른 말로 bean의 scope가 singleton이라고도 표현한다.

우선 왜 빈은 기본 scope가 singleton일까?

더보기

위에서도 말했지만, 스프링은 대부분 서버 환경에서 사용되고 매번 클라이언트에서 요청이 들어올 때마다 각 로직을 담당하는 오브젝트들을 새로 생성하기 어렵다. 따라서, 클래스당 하나의 오브젝트만 만들어두고 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다.

 

여기서 싱글톤이라는 것은 디자인 패턴에서 나오는 싱글톤 패턴과 비슷한 개념이지만 그 구현 방법은 확연히 다르다. 위에서도 언급했지만 Singleton pattern을 이용하여 구현하게 되면 여러 단점들이 생겨난다. 그래서 Spring은  Singleton registry 라는 것을 이용하여 싱글톤을 구현한다. 

Singleton Registry

스프링이 직접  싱글턴 객체를 만들고 관리하는 기능을 제공하는 것을 의미한다. 이 구현 방법은 static 메서드와 private 생성자를 이용하여 single object로 생성됨을 강제하지 않는다. 단지, 평범한 java class를 singleton으로 활용하게 해준다.

이 말인 즉슨, 평범한 public 생성자를 가진 자바 클래스를 싱글턴으로 활용할 수 있게 만들어준다.

그렇게 할 수 있는 이유는  클래스의 제어권을 IoC방식의 컨테이너에게 넘기면 해당 컨테이너가 객체 생성에 대한 모든 권한을 가질 수 있기 때문 이다. 따라서 객체가 싱글턴으로 존재할 수 있게 관리할 수 있다. 

 IoC 컨테이너의 제어 역할을 통해서 빈을 싱글톤으로 만들 수 있는 것이다.


 References