이전 포스트에서 싱글턴 객체를 생성하는 기본적인 방법과,
멀티 스레드 환경에서 동시성 이슈에 대응하여 싱글턴을 생성하는 방법에 대해 알아보았다.
하지만 이렇게만 하여도 완벽하게 동일한 객체를 생성하였다고 할 수 있을까?
기존에 생성한 싱글턴 구현을 깨는 방법과 그 방법들을 막는 방안에 대해 살펴보자.
직렬화, 역직렬화를 통한 객체 생성
먼저 Settings 객체에 직렬화 인터페이스를 구현한다.
public class Settings implements Serializable {}
public class App {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Settings settings = Settings.getInstance();
Settings settings1 = null;
try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("setting.obj"))) {
out.writeObject(settings);
}
try (ObjectInput in = new ObjectInputStream(new FileInputStream("setting.obj"))) {
settings1 = (Settings) in.readObject();
}
System.out.println(settings == settings1); //false
}
}
이러한 직렬화, 역직렬화 방식은 Settings 내에 메소드 오버라이딩으로 해결이 가능하다.
public class Settings implements Serializable {
//생략
//추가
protected Object readResolve() {
return getInstance();
}
}
Reflection 사용
자바의 리플렉션 기능을 사용하면 private 생성자로 생성을 방지하였음에도 불구하고 새로운 객체 생성이 가능하다.
public class App {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Settings settings = Settings.getInstance();
Constructor<Settings> constructor = Settings.class.getDeclaredConstructor();
constructor.setAccessible(true);
Settings settings1 = constructor.newInstance();
System.out.println(settings == settings1); //false
}
}
이 경우 Settings를 Enum으로 생성하면 해결이 가능하다.
public enum Settings {
INSTANCE;
}
public class App {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Settings settings = Settings.INSTANCE;
Settings settings1 = null;
Constructor<?>[] declaredConstructors = Settings.class.getDeclaredConstructors();
for (Constructor<?> constructor : declaredConstructors) {
constructor.setAccessible(true);
settings1 = (Settings) constructor.newInstance("INSTANCE");
}
}
}
애초에 newInstance 에서 예외가 발생한다.
또한 Enum은 기본적으로 Serializable 인터페이스를 구현하고 있는데, 이 부분 또한 방어가 되어 있다.
public class App {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Settings settings = Settings.INSTANCE;
Settings settings1 = null;
try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) {
out.writeObject(settings);
}
try (ObjectInput in = new ObjectInputStream(new FileInputStream("settings.obj"))) {
settings1 = (Settings) in.readObject();
}
System.out.println(settings == settings1); //true
}
}
이렇게 보면 Enum으로 싱글톤을 구현하는것이 완벽해보이지만..
아쉽게도 이 방법에도 한계가 존재한다.
- Enum은 클래스가 로드되는 시점에 미리 생성이 된다. Lazy 로딩 할 수 없다.
- 개발자가 상속을 사용해야 하는 객체에 대해서는 적용할 수 없다.
따라서 이러한 상황에서 싱글턴을 생성하고자 한다면, inner class 방식을 사용하는 것이 좋다.
참고 : 인프런, 백기선, 코딩으로 학습하는 GoF의 디자인 패턴(https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4)
코딩으로 학습하는 GoF의 디자인 패턴 강의 - 인프런
디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할
www.inflearn.com
'개발 > Java' 카테고리의 다른 글
[Java] Design-Pattern : ProtoType (0) | 2024.02.18 |
---|---|
[Java] Design-Pattern : Singleton(1) (0) | 2024.01.21 |
[Java] 예외의 종류(Checked, UnChecked, Error) (0) | 2023.07.30 |
[Java] Enum 에 대해 (0) | 2023.07.09 |
[Java] JVM - ClassLoader(아주 얕은 수준) (0) | 2022.09.03 |