본문 바로가기
프로그래밍/디자인 패턴

[Spring & Design Pattern] Spring에서 발견한 디자인패턴_Singleton Pattern

by 사바라다 2019. 9. 23.

안녕하세요. 오늘은 Spring에서 발견한 디자인 패턴의 3번째 시간으로 SingleTon 패턴에 대해서 알아보도록 하겠습니다.

SingleTon Pattern이란?

OOP에서 모든 객체들은 라이프 사이클(Life cycle)을 가지고 있습니다. 처음에 객체가 생성되고 GC에 의해서 삭제되기까지가 하나의 라이프 사이클입니다. 객체를 여러개를 만들지 않고 1개만 만들고 이를 공유해서 쓰고싶을 때가 있습니다. 이렇게 하면 객체 생성에 대한 비용 및 데이터 공유를 쉽게 할 수 있죠. 대표적인 예를 들면 DB의 Transaction을 관리하는 클래스가 그렇습니다. 이때 사용할 수 있는 pattern이 싱글톤 패턴(singleton pattern)입니다.

Singleton Pattern이란 인스턴스를 1개로 제한하며, global하게 어디서든 접근 할 수 있도록 하는 객체생성 디자인패턴입니다.

출처 : https://sourcemaking.com/design_patterns/singleton


Singleton Class는 본인을 호출하는 static method를 가지고 있으며, 이 method를 호출하면 본인을 반환하도록 설계됩니다.

바로 아래 예제를 구현해보도록 하겠습니다.

예제를 Java로 구현

출처 : https://sourcemaking.com/design_patterns/singleton

위 예제는 정부를 예로 든 것으로 정부는 1개의 인스턴스만 가질 수 있으며 재호출시 새로운 정부 인스턴스를 만드는 것이 아닌 기존에 있는 정부 인스턴스를 재반환 해야합니다.

package main.java.singleton;

public class Government {

    private static Government government;

    private Government()
    {
        System.out.println("직접호출 할 수 없습니다.");
    }

    public static Government election()
    {
        if(government == null)
        {
            government = new Government();
            return government;
        }
        return government;
    }

}

위의 소스코드를 보도록 하겠습니다. Government class가 있습니다. 위의 Class는 Singleton Class로 1개의 인스턴스만을 가질 수 있습니다.

  • Government class는 생성자가 private로 직접 인스턴스를 생성할 수 없습니다. (Reflection 제외)
    • Government government = new Government() [불가능]
  • Government class의 static 변수인 government는 private으로 외부에서 인스턴스를 주입할 수 없습니다.

위 2 제약조건 때문에 Government Class는 static method인 election을 통해서 instance를 얻을 수 밖에 없습니다. election에서는 첫 호출 시 instance를 주입 한 후 반환하며 2번째 부터는 주입 되어 있는 것을 반환하도록 되어있습니다.

package main.java.singleton;

public class Main {

    public static void main(String[] args) {

//        Government government = new Government(); // 오류

        Government government1 = Government.election();
        Government government2 = Government.election();
        Government government3 = Government.election();


        System.out.println("government1 = " + government1);
        System.out.println("government2 = " + government2);
        System.out.println("government3 = " + government3);
    }
}
직접호출 할 수 없습니다.
government1 = main.java.singleton.Government@cfb17cd
government2 = main.java.singleton.Government@cfb17cd
government3 = main.java.singleton.Government@cfb17cd

위의 2코드는 Government class를 사용하는 client class와 실행 결과를 나타냈습니다. 실행코드를 보면 각 호출에 대해 동일한 Instaance ID를 가지는 것을 보아 동일한 Instance라 것을 알 수 있습니다.

Spring에서의 Singleton Pattern

그렇다면 이런 Singleton Pattern은 Spring의 어디에서 사용될까요? 안타깝게도 singleton 패턴을 완전 위와 같이 사용하지는 않습니다. 왜냐하면 사실 Singleton 패턴은 안티패턴이기 때문입니다.

완전한 singleton 패턴은 아니지만 소스를 한번 보도록 하겠습니다.

spring의 bean들은 beanfactory에 의해서 관리되고 있습니다. 그리고 기본적으로 이러한 bean의 생명주기는 scope는 singleton을 따르고 있습니다. 즉, 우리가 getBean을 아무리 많이 하더라도 결국 1개의 인스턴스라는 이야기 입니다.

Spring Boot에서는 별도의 설정이 없다면 DefaultListableBeanFactory를 beanfactory의 기본으로 사용합니다. 우리가 getbean을 호출하면 이 클래스에서 bean을 가져온다는 말입니다. bean은 name, type 등 여러 방식으로 가져올 수 있는데 가져오는 method 중에서 하나인 resolveBean이라는 method를 보도록 하겠습니다.

@Nullable
    private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) {
        // 1. 등록되어있는 bean들의 이름을 검색한다.
        NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);

        // 2. 등록되어있다면 해당 bean을 리턴한다.
        if (namedBean != null) {
            return namedBean.getBeanInstance();
        }

        // 3. 다른 beanfactory에서 요청한 bean 찾기        
        BeanFactory parent = getParentBeanFactory();
        if (parent instanceof DefaultListableBeanFactory) {
            return ((DefaultListableBeanFactory) parent).resolveBean(requiredType, args, nonUniqueAsNull);
        }
        else if (parent != null) {
            ObjectProvider<T> parentProvider = parent.getBeanProvider(requiredType);
            if (args != null) {
                return parentProvider.getObject(args);
            }
            else {
                return (nonUniqueAsNull ? parentProvider.getIfUnique() : parentProvider.getIfAvailable());
            }
        }
        // 4. 찾지 못했을 시 null 반환
        return null;
    }

해당 메서드를 보면 우리가 예제로 만들어 보았던 Government class의 election의 메서드와 비슷한 메커니즘이라는 것을 알 수 있습니다.

  1. 등록되어있는 bean들의 이름을 검색
  2. 등록되어있다면 해당 bean의 instance를 반환
  3. 부모 beanfactory에게 해당하는 bean이 있는지 요청
  4. 찾지 못했을 경우 null 반환

하지만 private static 접근 제어자를 통한 singleton 패턴은 찾을 수 없었습니다.

왜 그런 걸까요?

안티 패턴?

일반적으로 사용하는 singleton 패턴은 안티패턴으로 알려져있습니다.

안티 패턴이란 습관적으로 많이 사용하는 패턴이지만 성능, 디버깅, 유지보수, 가독성 측면에서 부정적인 영향을 줄 수 있어 지양하는 패턴입니다.

그렇다면 이렇게 관리하기 좋은 Sigleton 패턴이 왜 안티패턴이라는 걸까요?

아래는 토비의 스프링을 참조하였습니다.

  • private 생성자를 갖고 있기 때문에 상속을 할 수 없다.
    • 자기자신만이 본인 오브젝트를 만들 수 있도록 제한하기 때문에 private 생성자를 사용했다. 하지만 이렇기 때문에 상속이 불가능하며, 이는 다형성 또한 제공할 수 없다는 뜻. 객체지향 설계를 적용할 수 없다는 것.
  • 테스트하기 힘들다.
    • 만들어지는 방식이 제한적이기 때문에 mock 오브젝트 등으로 대체하기 힘들다.
  • 서버환경에서는 1개의 instance를 보장하지 못한다.
    • 서버에서 클래스로더를 어떻게 구성하고 있느냐에 따라서 하나이상의 instnace가 만들어질 수 있다. 따라서 java환경에서 싱글톤이 보장된다할 수 없을 수 있다.
  • 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.
    • Sington은 어디에서든지 누구나 접근할 수 있다. 그리고 어디든지 사용될 수 있개 때문에 아무 객체가 자유롭게 접근하여 수정하고 데이터를 공유할 수 있는 상태가 될 수 있다는 것이다. 이러한 것은 객체지향에서는 권장되지 않는 프로그래밍 모델이다.

이러한 이유로 Spring에서도 직접 싱글톤패턴을 사용하지 않으며 SingleTon Registry 방식을 사용한다고 합니다.

마무리

오늘은 이렇게 Singleton 패턴에 대해서 이야기해보았습니다. Spring이라는 기술에 얼마나 많은 고민이 뭍어있는지 알 수 있었던 시간이었던것 같습니다.

다음에 다시 찾아뵙겠습니다.

감사합니다.

참고

토비의 스프링 3.1


https://sourcemaking.com/design_patterns/singleton

댓글