private 생성자나 enum 자료형은 싱글턴 패턴을 따르도록 설계하라
싱글턴은 객체를 하나만 만들 수 있는 클래스이다.
JDK1.5 이전에서 싱글턴을 구현하는 방법은 2가지가 존재한다.
하나는 생성자는 private로 선언하고, 싱글턴 객체는 정적(static) 멤버를 통해 이용하는 방법
public class corn{
public static final Corn INSTANCE = new Corn();
private Corn() {... }
public void run() { ... }
}
두번 째는 public으로 선언된 정적 팩터리 메서드를 이용하는 방법이다.
public class Corn{
private static final Corn INSTANCE = new Corn();
private Corn() { ... }
public static Corn getInstance() {return INSTANCE;}
public void run() { ... }
}
=> method를 이용하여 접근하기 때문에 좀더 좋은 성능을 낼 것 같지만, 최신 JVM은 정적 팩터리 메서드 호출을 거의 항상 inline으로 처리하기 때문에 성능상 차이거 거의 없다고 한다. (JVM관련하여 좀더 알아볼 필요가 있다.)
앞선 방법으로 생성자를 생성하였을 때의 문제점은
첫째, 리플렉션 공격에 위험할 수도 있다는 것이다.
AccessibleObject, setAccesible 메서드의 도움을 받아 권한을 획득한 클라이언트는 private 생성자를 호출 할 수 있다.
package practice.effective;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class PrivateInvoker {
public static void main(String[] args) throws Exception{
Corn instance = Corn.INSTANCE;
Corn instance2 = null;
// 리플렉션과 setAccessible 메서드를 통해 private로 선언된 생성자의 호출 권한을 획득
Constructor[] cons = Corn.class.getDeclaredConstructors();
for(Constructor con : cons){
con.setAccessible(true);
instance2 = (Corn) con.newInstance();
}
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
instance.run();
instance2.run();
}
}
결과 값
366712642
1829164700
run!!
run!!
==> 다른 객체가 생성됨
(이런 종류의 공격을 방어하기 위해서는, 두 번째 객체를 생성하라는 요청을 받으면 예외를 던지도록 생성자를 고쳐야 한다.)
둘째로는, 직렬화 가능 클래스를 만들기에 어려움이 있다는 것이다.
싱글턴 클래스를 직렬화 가능(Serializable)클래스로 만드려면 싱글턴 특성을 유지하기 위해 implements Serializable을 추가하고, 모든 필드를 transient로 선언하고, readResolve 메서드를 추가해야 한다.
그렇지 않으면 serialize 된 객체가 역직렬화(deserialize)될 때마다 새로운 객체가 생기게 된다.
JDK 1.5부터는 싱글턴을 구현할 때 새로운 방법을 사용할 수 있는데 원소가 하나뿐인 enum 자료형을 정의하는 것이다.
// Enum 싱글턴
public enum Corn{
INSTANCE;
public void run() {... }
}
위와 같이 구현하였을 때의 장점은
직렬화가 자동으로 처리된다는 것이다.
직렬화가 아무리 복잡하게 이루어져도 여러 객체가 생길 일이 없으며, 리플렉션(refelection)을 통한 공격에도 안전하다.
(원소가 하나뿐인 enum 자료형이야 말로 싱글턴을 구현하는 가장 좋은 방법이라고 effective java에서는 말하고 있음)
=> Thread-safe , Serialization 보장. Reflection을 통한 공격에도 안전
고로, 싱글턴 패턴을 사용할 때 effective Java에서는 enum을 활용한 싱글턴 구현을 추천하고 있다..
'프로그래밍 노트 > Effective 시리즈' 카테고리의 다른 글
익명 클래스보다는 람다를 사용하라. (0) | 2019.12.26 |
---|---|
규칙4. 객체 생성을 막을 때는 private 생성자를 사용하라 (0) | 2018.11.29 |
규칙1. 생성자 대신 정적 팩터리 메서드를 사용할 수 없는지 생각해 보라 (0) | 2018.11.26 |
규칙49. 객체화된 기본 자료형 대신 기본 자료형을 이용하라 (0) | 2018.11.14 |
규칙48. 정확한 답이 필요하다면 float와 double은 피하라 (0) | 2018.11.14 |