Search
▫️

싱글톤 패턴_2

싱글톤(Singleton)패턴

멀티 스레드 환경에서도 안전한 싱글톤 패턴을 자바로 구현하는 방법에 대해서 진행했지만,
사용하는 쪽에서 제대로 된 방법(우리가 생각한 방법)으로 사용하지 않고 (자바에서 허용하는 여러가지 방법 중) 개발자조차 생각지도 못한 방법으로 사용자가 진행하면서 싱글톤이 깨지게 되는 경우도 있다!

싱글톤 패턴 구현 방법을 깨트리는 방법

public class App { public static void main(String[] args){ Setting settings = Settings.getInstance(); Setting settings1 = Settings.getInstance(); System.out.println(settings == settings1); } }
Java
복사
Settings는 정상적으로 만든다면 우리가 만들어 놓은 방법으로 밖에 없다.
new Setting()를 해서 만들려고 하면 오류가 난다. 그래서 이 방법으로 밖에 못 만드는데
어떻게 하면 true가 나오고 false가 나오게 할 수 있을까?
대표적인 방법으로

리플렉션 사용하기

Settings.class.getDeclareConstructor()
Java
복사
를 가져와서
Constructor<Settings> constructor = Settings.class.getDeclareConstructor(); constructor.setAccessible(true);//private construtor가 있을거니까 접근 허용하게 해준다
Java
복사
setAccessible로 접근 허용까지 해주고
Settings settings1 = consconstructor.newInstance();
Java
복사
Settings 타입의 인스턴스를 만들 수 있다.
public class App { public static void main(String[] args){ Setting settings = Settings.getInstance(); Constructor<Settings> constructor = Settings.class.getDeclareConstructor(); constructor.setAccessible(true);//private construtor가 있을거니까 접근 허용하게 해준다 Settings settings1 = consconstructor.newInstance(); System.out.println(settings == settings1); } }
Java
복사
그러면 이제 이 인스턴스랑
//우리가 의도한 대로 만들어준 인스턴스 (Settings.java의 SettingsHolder가 가지고 있는 인스턴스) Setting settings = Settings.getInstance();
Java
복사
//new를 사용해서 생성자 호출해서 만든거랑 비슷한 인스턴스다. 즉 새로운 인스턴스 Constructor<Settings> constructor = Settings.class.getDeclareConstructor(); constructor.setAccessible(true);//private construtor가 있을거니까 접근 허용하게 해준다 Settings settings1 = consconstructor.newInstance();
Java
복사
이 인스턴스랑 다른 인스턴스가 된다
이제 false가 떨어진다.
이렇게 싱글톤을 깨트리는 첫번째 방법이였다.

직렬화 & 역직렬화 사용하기

자바에는 object를 파일 형태로 디스크에 저장했다가 다시 읽어드리는 기능이 있다
저장할때는 직렬화. 다시 읽어드릴때는 역직렬화라고 한다.
직렬화 할려면 우리가 Serializable를 implements 해주기.
(원래 id도 선언해줘야하는데 간단하게 할거라 패쓰~)
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; } }
Java
복사
Serializable 인터페이스를 구현한 것들은 직렬화, 역직렬화에 사용할 수 있다. 즉 이 객체를 파일로 저장했다가 다시 로딩할 수 있다.
public class App { public static void main(String[] args throws IOException){ Setting settings = Settings.getInstance(); try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) { out.writeObject(settings); } } }
Java
복사
이렇게 하면 settings.obj 객체가 생성된다. (객체 자체가 직렬화되서 파일로 써진거다)
역직렬화 해서 다시 읽어와보도록 한다
public class App { public static void main(String[] args throws IOException, ClassNotFoundException){ Setting settings = Settings.getInstance(); 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); } }
Java
복사
역직렬화를 하고 나면 반드시 생성자를 사용해서 다시 한번 인스턴스를 만들어 준다. 그래서 이게 또 다른 객체가 된다.
false로 떨어진다~
이렇게 여러가지 방법으로 보완해도 사용자쪽에서 이상한 방법으로 진행하면 깨질수 있다.
근데 역직렬화는 방법이 존재한다.

역직렬화 대응 방안

명시적으로 오버라이딩은 가능한게 아닌데 역직렬화 할때 사용할 수 있는 어떤 함수가 있다.
readResolve라는 시그니처를 가지고 있으면 역직렬화 할때 이 메소드를 반드시 사용한다.
protectes Object readResolve() { return getInstance(); }
Java
복사
원래는 new Settings를 하지만 지금 getInstance를 호출하게 바꿔줬다.
그러면 이제 동일한 객체를 얻을 수 있어서 true값으로 떨어진다
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; } protectes Object readResolve() { return getInstance(); } }
Java
복사
직렬화, 역직렬화는 반드시 getInstance()라는 함수를 호출하기 때문에 방지가 가능한데 여기서 리플렉션은 대응이 안된다. 그래서 여기서 이거까지 막을 수 있는 방법을 다음 시간에 이어서.. . . .

*참고(출처)