public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { .. } // `Elvis.INSTANCE` 초기화할 때 딱 한번만 호출
public void leaveTheBuilding() {..}
}
Elvis elvis = Elvis.INSTANCE;
AccessibleObject.setAccessible
==> 재호출시 예외를 발생시켜 방어 public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() { ... }
}
Elvis elvis = Elvis.getInstance();
Supplier<Elvis> elvisSupplier = Elvis::getInstance;
Elvis elvis1 = elvisSupplier.get();
System.out.println(elvis.equals(elvis1)); //true
위 장점이 필요X? -> 첫 번째 방식(public 필드 방식) 사용할 것
위 둘 중 하나의 방식으로 만들어진 싱글턴 클래스를 직렬화하여 싱글턴임을 보장하기 위해(인스턴스가 한번만 생성되기 위해)서는 readResolve()
를 제공 해야한다.(item-89)
/**
* 싱글턴임을 보장해주는 readResolve 메서드
* @return
*/
private Object readResolve() {
//'진짜' Elvis 를 반환하고, 가짜 Elvis 는 가비지 컬렉션에 맡긴다.
return INSTANCE;
}
이게 무슨 의미인지 이해가 안돼서 직접 예제 작성해봤다. 궁금하다면 아래내용으로 이동🌝
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
Elvis elvis = Elvis.INSTANCE;
readResolve()
필요 X) 직렬화 가능, 리플렉션 공격도 방어 가능위까지가 책 내용이공 아래는 궁금해서 찾아본 것들😙
코드 돌려보기 전에
자바 내부 시스템에서 사용되는 객체나 데이터를 외부에서 사용하기 위해서는 외부에 해당 객체나 데이터 형식으로 값을 전달할 수 없고 Byte 형태로 전달해야 한다. Byte 형태로 만들어주는 것을 Serialize(직렬화)라고 하며 이를 다시 사용 가능한 데이터나 객체 형식으로 만들어주는 것을 Deserialize(역직렬화)라고 한다.
실제 코드를 돌려보자!!
아래와 같이 readResolve()
추가하지 않은 Serialize를 구현하는 클래스가 있다고 하자.
public class ElvisSerialize implements Serializable {
private static final ElvisSerialize INSTANCE = new ElvisSerialize();
private ElvisSerialize() { }
public static ElvisSerialize getInstance() { return INSTANCE; }
public void leaveTheBuilding() {
..
}
}
해당 직렬화를 테스트하는 코드가 아래와 같다.(요런 직렬화&역직렬화 예제는 인터넷에 많당)
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ElvisSerializeTest {
public byte[] serialize(Object instance) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (bos; ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(instance);
} catch (Exception e) {}
return bos.toByteArray(); //외부에서 읽을 수 있는 바이트 형식 반환
}
public Object deserialize(byte[] serializedData) {
ByteArrayInputStream bis = new ByteArrayInputStream(serializedData);
try (bis; ObjectInputStream ois = new ObjectInputStream(bis)) {
return ois.readObject(); //사용가능한 데이터 반환
} catch (Exception e) {}
return null;
}
public static void main(String[] args) {
ElvisSerialize instance = ElvisSerialize.getInstance();
ElvisSerializeTest serializationTester = new ElvisSerializeTest();
byte[] serializedData = serializationTester.serialize(instance);
ElvisSerialize result = (ElvisSerialize) serializationTester.deserialize(serializedData);
System.out.println("instance == result : " + (instance == result));
//instance == result : false
System.out.println("instance.equals(result) : " + (instance.equals(result)));
//instance.equals(result) : false
}
}
싱글턴이기 때문에 두 객체가 같다라고 나오는 것을 예상했지만 false가 떨어짐을 알 수 있다.
직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어진다.
이를 해결하기 위해서는 Serialize를 구현하는 ElvisSerialize 클래스에서 readResolve()
를 추가하면 된다.
public class ElvisSerializeResolve implements Serializable {
private static final ElvisSerializeResolve INSTANCE = new ElvisSerializeResolve();
private ElvisSerializeResolve() { }
public static ElvisSerializeResolve getInstance() { return INSTANCE; }
public void leaveTheBuilding() { ... }
/**
* 싱글턴임을 보장해주는 readResolve 메서드
* @return
*/
private Object readResolve() {
//'진짜' Elvis 를 반환하고, 가짜 Elvis 는 가비지 컬렉션에 맡긴다.
return INSTANCE;
}
}
readResolve()
를 추가후 다시 직렬화 예제 코드를 돌려본다.
public static void main(String[] args) {
ElvisSerializeResolve instance = ElvisSerializeResolve.getInstance();
ElvisSerializeResolveTest serializationTester = new ElvisSerializeResolveTest();
byte[] serializedData = serializationTester.serialize(instance);
ElvisSerializeResolve result = (ElvisSerializeResolve) serializationTester.deserialize(serializedData);
System.out.println("instance == result : " + (instance == result));
//instance == result : true
System.out.println("instance.equals(result) : " + (instance.equals(result)));
//instance.equals(result) : true
}
같은 인스턴스라는 true가 떨어지면서 싱글턴임을 보장됨을 확인했다.🥺