반응형

싱글턴 패턴으로 이루어진 객체는 new로 생성이 불가능 하고 싱글턴 객체 자체를 mocking할 방법이 없기 때문에 테스트하기가 어렵다.

 

아래에서 싱글턴을 만드는 방법을 알아보자.

 

1. public static final 필드를 이용한 방식의 싱글턴

public class SingletonClass {
    public static final SingletonClass INSTANCE = new SingletonClass();
    
    private SingletonClass() {}
}

 

public class SingletonClassTest {

    @Test
    public void test(){
        SingletonClass c1 = SingletonClass.INSTANCE;
        SingletonClass c2 = SingletonClass.INSTANCE;

        assertEquals(c1, c2);
    }
}

위 코드처럼 INSTANCE를 호출하게 되면 SingletonClass에서 final로 한번만 생성되기 때문에 c1과 c2는 결국 같은 객체가 된다.

 

 

단점

 

reflection API의 setAccecessible()을 통해 private 생성자를 호출 하여, 객체가 중복되어 생성될 수 있다.

public class SingletonClassTest {

    @Test
    public void test() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        SingletonClass c1 = SingletonClass.INSTANCE;
        SingletonClass c2 = SingletonClass.INSTANCE;

        assertEquals(c1, c2);

        Constructor<SingletonClass> constructor = (Constructor<SingletonClass>) c2.getClass().getDeclaredConstructor();
        constructor.setAccessible(true);
        SingletonClass c3 = constructor.newInstance();
        assertEquals(c1, c3);
    }
}
java.lang.AssertionError: 
Expected :com.demo.testing.mockito.case1.SingletonClass@3f8f9dd6
Actual   :com.demo.testing.mockito.case1.SingletonClass@aec6354

 

따라서 아래와 리플렉션을 막기 위해 아래와 같이 SingletonClass를 수정해야한다.

 

public class SingletonClass {
    public static final SingletonClass INSTANCE = new SingletonClass();

    private SingletonClass() {
        if(INSTANCE != null){
            throw new RuntimeException("INSTANCE can not be duplicated.");
        }
    }
}

 

 

2. 정적 팩토리 방식의 싱글턴

class SingletonClass {
    private static final SingletonClass INSTANCE = new SingletonClass();
    
    private SingletonClass() {}
    
    public static SingletonClass getInstance() {
        return INSTANCE;
    }
}

 

팩토리 메서드만 수정하면 언제든지 싱글톤이 아니게 바꿀 수 있으며,

제네릭 싱글턴 팩터리로 만듦으로써 타입에 유연하게 대처할 수 있다.

또한, static 팩토리 메소드를 Supplier에 대한 메소드 레퍼런스로 사용할 수도 있다.

Supplier<SingletonClass> sc = SingletonClass::getInstance;

 

 

싱글턴 객체의 직렬화

 

싱글턴 클래스를 직렬화 하기 위해서는 Serializable을 구현한다고 선언하는 것 만으로는 부족하다.
모든 인스턴스 필드에 transient 예약어를 붙여 직렬화를 막은 다음 readResolve 메서드를 제공해야 한다.
이렇게 하지 않으면 역직렬화 시점에 새로운 인스턴스가 생성 된다.

public class SingletonClass implements Serializable {
    private static final SingletonClass INSTANCE = new SingletonClass();
    private SingletonClass() {}
    public static SingletonClass getInstance() {
        return INSTANCE;
    }

    //역직렬화가 되어 새로운 인스턴스가 생성되더라도,  
    //클래스간 공유 변수인 static 변수를 이용하면 싱글턴을 보장 할 수 있다. 
    // 새로운 인스턴스는 GC에 의해 UnReachable 형태로 판별되어 제거된다. 
    public Object readResolve() {
        return INSTANCE;
    }
}

 

 

 

3. enum을 이용한 싱글턴 생성

public enum Singleton {
  INSTANCE;
}

enum으로 만드는 싱글턴은 가장 안전하고 좋은 방법이다. 복잡한 직렬화 상황이나, 리플렉션 공격에도 안전하다. 

단, 만들려는 싱글턴이 Enum이외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다.
(열거 타입이 다른 인터페이스를 구현하도록 하는 건 가능.)

 

 

a1010100z.tistory.com/164

velog.io/@ajufresh/private-%EC%83%9D%EC%84%B1%EC%9E%90%EB%82%98-enum-%ED%83%80%EC%9E%85%EC%9C%BC%EB%A1%9C-%EC%8B%B1%EA%B8%80%ED%84%B4%EC%9E%84%EC%9D%84-%EB%B3%B4%EC%A6%9D%ED%95%98%EB%9D%BC

반응형