✍️ 싱글톤을 깨트리는 방법
자바에서 허용하는 문법을 이용하면 싱글톤을 깨트릴 수 있다.
아래 코드에서 몇 가지 자바 문법을 사용하면 settings1과 settings2의 비교 결과가 false가 될 수 있다.
public class Settings {
private Settings() {
}
private static class SettingsHolder {
private static final Settings INSTANCE = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.INSTANCE;
}
}
public class App {
public static void main(String[] args) {
Settings settings1 = Settings.getInstance();
Settings settings2 = Settings.getInstance();
System.out.println(settings1 == settings2);
}
}
// true
1, 리플렉션
자바의 리플렉션을 이용하면 클래스의 생성자를 받아올 수 있고, 받아온 생성자의 newInstance를 통해 인스턴스를 생성할 수 있다. 단, 여기서 생성된 인스턴스는 Holder가 가지고 있는 인스턴스와는 전혀 다른 새로운 인스턴스다.
public class App {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Settings settings1 = Settings.getInstance();
Constructor<Settings> constructor = Settings.class.getDeclaredConstructor();
constructor.setAccessible(true);
Settings settings2 = constructor.newInstance();
System.out.println(settings1 == settings2);
}
}
// false
2-1, 직렬화 & 역직렬화
직렬화란 오브젝트를 파일 형태로 디스크에 저장하는 것이다. 반대로 다시 읽어들이는 것을 역직렬화라고 한다.
기본적으로 Serializable 인터페이스를 구현한 클래스의 인스턴스들은 직렬화와 역직렬화에 사용할 수 있다. 이 말인즉슨 인스턴스를 파일로 저장했다가 다시 읽어올 수 있다는 뜻이다.
직렬화
public class App {
public static void main(String[] args) throws Exception {
Settings settings1 = Settings.getInstance();
try(ObjectOutput output = new ObjectOutputStream(new FileOutputStream("settings.obj"))){
output.writeObject(settings1);
}
}
}
// settings.obj
�� sr Singleton.Settings����q� xp
역직렬화
public class App {
public static void main(String[] args) throws Exception {
Settings settings1 = Settings.getInstance();
try(ObjectOutput output = new ObjectOutputStream(new FileOutputStream("settings.obj"))){
output.writeObject(settings1);
}
Settings settings2 = null;
try(ObjectInput input = new ObjectInputStream(new FileInputStream("settings.obj"))){
settings2 = (Settings) input.readObject();
}
System.out.println(settings1 == settings2);
}
}
역직렬화를 할 때 반드시 생성자를 통해 다시 한번 인스턴스를 만들기 때문에 직렬화에 사용한 인스턴스와는 전혀 다른 인스턴스가 된다.
2-2, 역직렬화 대응 방안
역직렬화의 대응 방안으로 readResolve가 있다. readResolve를 정의하면 readObject를 통해 만들어진 인스턴스 대신 readResolve가 반환하는 인스턴스가 역직렬화의 결과물이 된다.
public class Settings implements Serializable {
private Settings() {
}
private static class SettingsHolder {
private static final Settings INSTANCE = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.INSTANCE;
}
protected Object readResolve(){
return SettingsHolder.INSTANCE;
}
}
🍊 리플렉션을 막을 순 없을까?
직렬화 & 역직렬화는 readResolve를 구현함으로써 대응할 수 있었지만, 지금까지의 싱글톤 구현 방법으론 리플렉션에 대응하진 못한다.
그렇다면 리플렉션은 절대 방어하지 못하는 걸까?
'Java > Design Pattern with Java' 카테고리의 다른 글
[객체 생성 패턴] Chapter 2-1. Factory Method Pattern : 패턴 소개 (0) | 2022.03.31 |
---|---|
[객체 생성 패턴] Chapter 1-5. Singleton Pattern : 자바에서 찾아보는 싱글톤 (0) | 2022.03.28 |
[객체 생성 패턴] Chapter 1-4. Singleton Pattern : 안전하고 단순한 싱글톤 (0) | 2022.03.28 |
[객체 생성 패턴] Chapter 1-2. Singleton Pattern : 멀티 쓰레드에서도 안전하게 (0) | 2022.03.28 |
[객체 생성 패턴] Chapter 1-1. Singleton Pattern : 가장 단순한 구현 (0) | 2022.03.28 |