싱글턴을 만드는 가장 안전한 방법은 Enum 을 사용하는 것이다.
싱글턴이란?
인스턴스를 오직 하나만 생성할 수 있는 클래스
- 무상태 클래스
- 설계상 유일해야하는 시스템 컴포넌트
싱글턴 생성 방법
1. public static final 필드 사용
생성자 private, 접근 수단으로 public static final 멤버 제공 (초기화 할 때만 호출되어 싱글턴 보장)
장점
- 싱글턴임이 명백하게 드러남
- 간결한 코드
문제점
- 리플렉션 API인 AccessibleObejct.setAccessible을 사용하여 private 생성자를 호출하는 공격 가능
- 따라서 생성자에 두 번째 객체가 생성될 때 예외를 던지게끔 수정 필요
public class Elvis {
private static final Elvis INSTANCE = new Elvis(); // 초기화시 한 번 생성
private Elvis(){ ... }
}
2. 정적 팩터리 메서드 사용
생성자 private, 접근 수단으로 getInsance() 정적 팩터리 메서드 제공 (final 필드를 반환)
장점
- 필요시 API를 바꾸지 않아도 싱글턴이 아니게 변경 가능
- 원하는 경우 정적 팩터리를 제너릭 싱글턴 팩터리로 생성 가능
- 정적 팩터리의 메소드 참조를 공급자로 사용 가능
문제점
- 1번의 리플렉션 공격 가능
public class Elvis {
private static final Elvis INSTANCE = new Elvis(); // 초기화시 한 번 생성
// private 생성자
private Elvis(){ ... }
// 정적 팩터리 메서드
public staic Elvis getInstance() { return INSTANCE; }
}
1, 2 방식의 문제점
역직렬화 할 때마다 새로운 객체가 생성되어 싱글턴이 깨진다.
역직렬화할 때 생성자를 거치지 않고 새로운 객체를 만들기 때문이다.
직렬화: 객체 → 바이트 스트림
역직렬화: 바이트 스트림 → 객체
이를 해결하기 위해서는 readResolve() 메서드를 추가해야한다.
readResolve(): 역직렬화 시 새 객체를 만드는 대신 기존 객체를 반환
import java.io.Serializable;
public class Singleton implements Serializable {
public static final Singleton INSTANCE = new Singleton();
private Singleton() {}
// 역직렬화 시 싱글턴 유지
protected Object readResolve() {
return INSTANCE;
}
}
3. enum 타입 사용
- 가장 안전하고, 좋은 방법
- 역직렬화시에도 새 객체 생성하지 않음 → readResolve() 필요 없음
- 리플렉션 공격 시 생성자 강제 호출 불가 → 새 객체 안만들어짐
public enum Elvis {
INSTANCE;
}
만들려는 싱글턴이 Enum 외 상속을 해야하는 경우에는 사용할 수 없다.
반응형
'도서 > 이펙티브 자바' 카테고리의 다른 글
| [이펙티브 자바] 6. 불필요한 객체 생성을 피하라 (0) | 2025.03.20 |
|---|---|
| [이펙티브 자바] 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2025.03.04 |
| [이펙티브 자바] 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2025.03.03 |
| [이펙티브 자바] 2. 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2025.02.21 |
| [이펙티브 자바] 1. 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2025.02.20 |