개발하는 두더지

[Effective Java 규칙3] private 생성자나 enum 자료형은 싱글턴 패턴으로 설계 본문

Java,Android

[Effective Java 규칙3] private 생성자나 enum 자료형은 싱글턴 패턴으로 설계

덜지 2018. 9. 4. 17:28

Effective Java 2/E 책과 구글링을 통해 내용을 정리하고 개인적인 견해가 포함된 글입니다.


싱글턴은 유일한 객체이므로 클래스를 싱글턴으로 만들 경우 테스트하기가 어렵다. 왜냐하면 가짜 구현으로 대체할 수 없기 때문에

싱글턴을 구현하는 2가지 방법


1. private 생성자, public static 멤버변수 객체 생성

2. private 생성자, public static 멤버변수 객체 생성, public 정적 팩터리 메서드 이용


싱글턴 클래스를 직렬화 가능 클래스로 만들려면 클래스 선언에 implements Serializable을 추가하면 되는데

위 2가지 방법의 경우 추가하는 것만으로는 부족하다. 싱글턴 특성을 유지하려면 모든 필드를 transient (순간적인/일시적인?) 로 선언하고

readResolve 메서드를 추가해야한다. 그렇지않으면 직렬화된 객체가 역직렬화될 때마다 새로운 객체가 생성되기 때문이다.


// 싱글턴 상태를 유지하기 위한 메서드
private Object readResolve() {
// 동일한 객체가 반환되도록 하는 동시에 가짜 객체는
// GC가 처리하도록 만든다.
return INSTANCE;
}


위 2가지 방법은 리플렉션( reflection ) 공격에 취약하다

public class PrivateInvoker {
public static void main(String[] args) throws Exception {
Constructor<?> con = Private.class.getDeclaredConstructor();
con.setAccessible(true);
Private p = (Private) con.newInstance();
}
}

public class Private {
private Private() {
System.out.println("Hello effective Java");
}
}

이렇게 private 으로 선언된 생성자도 권한만 쥐어준다면 호출할 수 있다.

그런데 어디서 이런공격을 한다는건지는 찾아봐야함.. 


위 2가지 방법은 클래스 로딩시점에 객체가 생성되기 때문에 무거운 클래스라면 시작하자마자 많은 메모리를 할당하게 된다.

이를 대안하는 방법에는 객체를 사용하는 시점에 객체를 생성하는 방법이다

최초 사용시점에 인스턴스화 시키기때문에 메모리에 적재되는 시점에 부담이 많이 줄게 된다.

하지만 멀티 스레드 방식이라면 안전하지 않으므로 syncronized를 넣어서 해결할 수 있다.

class LazySingleton {
private static LazySingleton INSTANCE;
private LazySingleton() {}

public static LazySingleton getInstance() {
if(INSTANCE == null)
INSTANCE = new LazySingleton();
return INSTANCE;
}
}



JDK 1.5 이후부터는 싱글턴을 구현할 때 원소가 하나인 enum 자료형으로 정의하는 것이다.

enum Elvis {
INSTANCE;
public void leaveTheBuilding() {}
}

직렬화가 아무리 복잡해도 여러 객체가 생길일이 없으며, 멀티스레드에도 안전하며, 리플렉션을 통한 공격에도 안전하다


Effective Java에 이렇게 나온다

"원소가 하나뿐인 enum 자료형이야말로 싱글턴을 구현하는 가장 좋은 방법이다."


이렇게 튼튼하고 안전한 방식이 왜 널리 사용되지는 않은걸까?



public class PrivateInvoker {

   public static void main(String[] args) throws Exception {

       Constructor<?> con = Private.class.getDeclaredConstructor();

       con.setAccessible(true);

       Private p = (Private) con.newInstance();

   }

}


public class Private {

   private Private() {

       System.out.println("Hello effective Java");

   }

}


p25 주석

https://stackoverflow.com/a/4081508/6602341


무서운 카메라의 경우(?)

사람들이 잘 쓰지않는 잘 알려지지 않는 API를 리플렉션을 통해서 private한 클래스 생성자, 메서드를 사용할 수 있다.

안드로이드 sdk, 3rd party library를 리플렉션을 통해 마법같이 사용할 수 있다. 물론 권장되는 방법은 아니다.

무서운 카메라는 소리가 나게하는 메서드를 리플렉션하여 소리를 안나게 만들었다. (?) 찾아보고 정리할 것
















Comments