Skip to content

Item 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라. #1

@youngkimi

Description

@youngkimi

Chapter : 2. 객체 생성과 파괴

Item : 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라.

Assignee : youngkimi


🍑 서론

싱글턴을 구현하는 여러가지 방법에 대해 비교 설명한다.

🍑 본론

싱글턴(Singleton)

  • 인스턴스를 오직 하나만 생성할 수 있는 클래스이다.
  • 함수와 같은 무상태 객체나 설계 상 유일해야하는 시스템 컴포넌트.
  • 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기 어려워질 수 있다. (가짜 구현이 불가능하므로)

싱글턴을 생성하는 방법

1. Instance를 public static final 로 설정.

public class Singleton1 {
    public static final Singleton1 singleton1 = new Singleton1();
    private Singleton1() {}
}
  • 초기화 시에만 인스턴스를 할당하는 방법. 생성자는 private이라 외부에서 인스턴스를 새로 생성할 수 없다.
  • 권한이 있는 클라이언트는 Reflection API를 활용해서 private 생성자를 호출할 수 있다. Class.class.getConstructor() 통해서.
  • 이를 해결하기 위해서는 생성자 호출 시 증가하는 멤버 변수를 하나 두고, 생성자 호출 시 멤버 변수가 1 이상이라면 예외를 던져서 막을 수 있다.
  • 해당 클래스가 싱글턴임이 API에 드러난다.

2. 정적 팩터리 메서드를 public static 멤버로 제공.

public class Singleton2 {
    private static final Singleton2 instance = new Singleton2();

    private Singleton2() {}

    public static Singleton2 getInstance() {
        return instance;
    }
}
  • instance에 직접 접근하지 못하게 하고, instance에 접근 가능하도록 getInstance()를 제공한다.
  • getInstance()는 항상 같은 객체 참조를 반환하므로 두 번째 instance는 만들어지지 않는다. (물론 위와 같은 예외는 여전히 발생한다.)
  • 위 방법보다 간결하다. 싱글턴이 아니게 변경해도 API를 수정할 필요가 없다.
  • 싱글턴이 아닌 경우.
public Singleton2 getInstance() {
        return new Singleton2();
    }
  • 두 경우 모두 API에서 instance를 호출하는 방식은 다음과 같을 것이다.
Singleton2 singleton2 = Singleton2.getInstance();
  • 때에 따라서 멀티 쓰레드 환경에서 인스턴스가 두 개 생성될 수도 있다.
  • 이 때에는 Lazy initialization으로 방지할 수 있다.
public class Singleton {

    private static volatile Singleton instance = null;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 정적 팩터리의 메서드 참조를 공급자(Supplier)로 사용할 수 있다.
  • Supplier
    • 지연 로딩 제공. 코드 가독성 제고, 동적 생성, 함수형 프로그래밍과 호환...
  • 직렬화 시 발생하는 문제점
    • 둘 중 하나의 방식으로 만든 싱글턴 클래스를 직렬화하면 이후 역직렬화할 때 다중 인스턴스가 생성될 수 있다.
    • 모든 인스턴스 필드를 transient로 선언하고 readResolve()메서드를 제공해야 한다.
    • transient는 Serialize하는 과정에 제외하고 싶은 경우 선언하는 키워드입니다.
    private static final transient Singleton instance = new Singleton();
    
    public Object readResolve() {
        // readResolve는 역직렬화 시 호출되는 메서드. 
        // 역직렬화 과정에서 만들어진 인스턴스 대신에 기존에 생성된 싱글톤 인스턴스를 반환하도록 한다. 
        return instance;
    }

3. 원소가 하나인 열거 타입 선언

public enum Singleton3{
    INSTANCE;
}
  • public 필드 방식과 비슷하지만, 간결하고 추가 노력 없이 직렬화할 수 있다.
  • 직렬화 상황이나 Reflection 공격에도 인스턴스 생성을 완벽히 방지해준다.
  • 하지만 만들려는 싱글턴이 Enum 외의 클래스를 상속하는 경우 이 방법은 사용할 수 없다.
  • 근데 현실적으로 적용 가능할까?

스프링과 싱글턴

  • 스프링 사용 시 보통 Service, Repository 등 모두 Bean을 싱글턴으로 등록해서 사용한다.
  • 여기서 싱글턴은, 하나의 ApplicationContext 에서 싱글턴이다.
  • Scope가 바뀌면, 싱글턴이 아닐 수 있다. (prototype, session ...)
  • 스프링에서 Bean 싱글턴 관리는 enum으로 생성한 싱글턴과 다를 수 있고, 의존성 주입에 문제가 있을 수 있다. 테스트에도 어려움이 있을 것이다.

🍑 결론


Referenced by

Metadata

Metadata

Assignees

Labels

🐳 02 Objects2장 객체 생성과 파괴🙇🏻 help wanted맡은 주제를 잘 모르겠어요. 도움이 필요해요.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions