슬픈 야옹이 2024. 4. 8. 22:55

웹 어플리케이션과 싱글톤

스프링은 기업용 온라인 서비스 개발을 지원하기 위해 탄생한 프레임워크다.

 

물론 스프링으로 웹 어플리케이션 뿐 아니라 다양한 어플리케이션을 개발할 수 있지만,

스프링은 웹 어플리케이션을 개발하는 데 최적화되어 있다.

 

웹 어플리케이션의 특징이 무엇이냐 하면, 다수의 클라이언트가 동시에 요청을 한다는 점이다.

즉 스프링이 웹 어플리케이션 개발에 최적화되어 있다는 얘기는, 다수의 요청을 동시에 처리하는데 특화되어 있다는 말과 같은 의미다.

다수 클라이언트의 동시 요청 (강의자료 발췌)

 

다수의 요청을 동시에 처리할 때, 각 요청에 대해 일일이 객체를 생성해서 응답하면 메모리 공간이 크게 낭비될 것이다.

당장 요청을 100개만 처리한다고 해도 객체 100개가 새로 생성되고 소멸된다는 이야기인데, 결코 효율적이라 할 수 없다.

 

이 때 유용한 것이 싱글톤 패턴이다. 싱글톤 패턴을 이용하면 다수의 요청에 대해 하나의 객체를 공유하여 처리할 수 있다.

 

그리고 스프링 역시 싱글톤 패턴을 이용해 다수의 요청을 효율적으로 동시에 처리한다.

 

 

 

싱글톤 패턴

싱글톤 패턴이란, 클래스 인스턴스가 단 1개만 생성되는 것을 보장하는 디자인 패턴을 말한다.

 

만드는 방법은 간단하다.

  • static 영역에 클래스 인스턴스를 하나 생성한다.
  • 생성한 인스턴스를 리턴하는 메서드를 하나 작성한다.
  • 생성자를 private로 선언해 외부에서 새로운 인스턴스를 생성하지 못하도록 막는다.

이를 코드로 작성한 예시는 다음과 같다.

SingletonService.java

package hdxian.hdxianspringcore.singleton;

public class SingletonService {

    // 관례상 싱글톤 인스턴스 이름은 instance.
    // static으로 선언 -> 클래스 레벨에서 로드됨.
    private static final SingletonService instance = new SingletonService();

    private SingletonService() {
        // 생성자를 private로 선언해서 외부에서 SingletonService 객체를 생성하지 못하도록 한다.
    }

    public static SingletonService getInstance() {
        return instance;
    }

    public void logic() {
        System.out.println("singletonService의 로직 실행");
    }

}

 

instance라는 이름의 참조변수 하나를 멤버로 선언하고, 객체를 생성해서 하나 집어넣는다.

 

SingletonService() 생성자는 private로 선언하여 외부에서 접근하지 못하도록 한다.

 

getInstance() 메서드를 선언하여 instance를 리턴하도록 작성한다.

 

그 외에 해당 인스턴스로 실행할 작업들(logic() 등)을 별도로 작성한다.

 

 

해당 클래스를 바탕으로 테스트를 돌려보면, 같은 객체임을 알 수 있다.

@Test
    @DisplayName("싱글톤 패턴 적용 사례 테스트")
    void singletonServiceTest() {
        SingletonService singletonService1 = SingletonService.getInstance();
        SingletonService singletonService2 = SingletonService.getInstance();

        System.out.println("singletonService1 = " + singletonService1);
        System.out.println("singletonService2 = " + singletonService2);

        // same: == (참조 비교)
        // equal: equals() (내용 비교)
        assertThat(singletonService1).isSameAs(singletonService2);
    }

 

테스트 결과

 

싱글톤 패턴을 구현하는 방법은 여러가지가 있다.

예시의 방법은 클래스 로드 시점부터 인스턴스를 미리 생성해놓는 방식이고,

 

처음에는 인스턴스를 생성하지 않고 있다가 해당 클래스 인스턴스를 참조할 때 생성하는 지연 처리 방식도 있다.

 

 

결론적으로, 싱글톤 패턴을 적용하면 클라이언트 요청이 올 때마다 새로운 객체가 아닌 객체의 참조를 리턴하므로

훨씬 효율적으로 자원을 사용할 수 있다.

 

하지만 싱글톤 패턴은 좋아보이면서도, 다음과 같은 여러가지 문제점을 가지고 있다.

  • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다. ( [구체 클래스].getInstace() )
  • 클라이언트가 구체 클래스에 의존하므로 OCP 원칙을 위반할 가능성이 높다.
  • 테스트가 어렵다.
  • 내부 속성을 변경하거나 초기화하기 어렵다.
  • 자식클래스를 만들기 어렵다. (private 생성자)
  • 유연성이 떨어진다.
  • 안티패턴으로 불리기도 한다.

 

 

그렇다면 싱글톤 패턴을 사용하는 스프링도 이러한 문제점이 있는 것일까?

 

다행스럽게도, 스프링 컨테이너를 이용하면 이러한 싱글톤 패턴의 문제점들을 해결하면서 싱글톤 패턴을 활용할 수 있다.