본문 바로가기
Today I Learned/Spring

[Spring] 싱글톤 패턴(Singleton Pattern)

by 프로그래 밍구 2023. 11. 1.

요청 시 객체를 생성하는 구조

 웹 어플리케이션이 있다. 고객들이 요청할 때마다 AppConfig(스프링 없는 순수한 DI 컨테이너)는 객체를 새로 생성할 것이다. 100개의 요청이 들어오면 100개의 객체가 생성이 되고 소멸 될 것이다. 이 서비스가 인기가 더 많아지면 초당 10,000개 이상의 객체를 생성하고 소멸될 수도 있다. 다음은 스프링이 없는 순순한 DI 컨테이너를 나타낸 코드이다.

 

public class SingletonTest {

    @Test
    void pureContainer() {
        AppConfig appConfig = new AppConfig();

        MemberService memberService1 = appConfig.memberService();
        MemberService memberService2 = appConfig.memberService();

        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);

        assertThat(memberService1).isNotSameAs(memberService2);
    }
}

// 테스트 실행 결과
// memberService1 = hello.core.member.MemberServiceImpl@7494f96a
// memberService2 = hello.core.member.MemberServiceImpl@561b6512

 

 appConfig가 생성한 memberService1memberService2가 있다. memberService1memberService2를 비교하였을 때 서로 다른 객체이기 때문에 테스트를 통과하였고, 출력된 결과에서도 서로 다른 메모리 주소를 가진 것을 확인할 수 있다. 만약 memberService 객체가 10,000개 이상이라면 이렇게 객체가 생성되고 소멸되는 과정은 메모리 관점에서 낭비일 것이다. 이를 해결하는 방안은 객체는 처음 한 번만 생성이 되고, 그 객체를 공유를 하도록 설계하는 것인데 이것을 싱글톤 패턴이라고 한다.

 


싱글톤 패턴(Singleton Pattern)

 싱글톤 패턴을 따르는 클래스는 생성자가 여러번 호출되어도 실제로 생성되는 객체는 하나뿐인 디자인 유형을 말한다. 간단하게 클래스의 인스턴스가 1개만 생성되는 것을 보장하는 디자인 패턴이다. 싱글톤 패턴을 적용하면 하나의 메모리 영역만 이용하기 때문에 메모리 낭비를 방지할 수 있고, 생성된 인스턴스를 다시 사용하는 것이므로 속도에서도 이점이 있을 수 있다. 다음은 싱글톤 패턴이 적용된 간단한 코드이다.

 

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance() {
        return instance;
    }

    private SingletonService() {
    }

    public void logic() {
        System.out.println("싱글톤 객체 로직 호출");
    }
}

 

 static 영역에 인스턴스를 하나 생성해놓은 코드이다. 이 객체가 필요하면 getInstance( ) 메서드를 통하여 조회할 수 있고, 이 메서드는 항상 같은 인스턴스를 반환할 것이다. 객체 인스턴스는 하나만 생성되어야 하므로 private 생성자로 외부에서 new 키워드를 이용한 객체 생성을 방지하였다. 

 

public class SingletonTest {
    @Test
    void SingletonServiceTest() {
        SingletonService singletonService1 = SingletonService.getInstance();
        SingletonService singletonService2 = SingletonService.getInstance();

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

        assertThat(singletonService1).isSameAs(singletonService2);
    }
}

//테스트 실행 결과
//singletonService1 = hello.core.singleton.SingletonService@7ee8290b
//singletonService2 = hello.core.singleton.SingletonService@7ee8290b

 

 테스트 결과 호출될 때마다 같은 객체 인스턴스를 반환하는 것을 확인할 수 있다. 이처럼 이미 만들어진 인스턴스를 공유하는 싱글톤 패턴은 효율적이나, 다음과 같은 몇 가지 문제가 존재한다.

 


싱글톤 패턴의 문제점

  • 싱글톤 패턴을 구현하는 코드가 필요하다.
  • 클라이언트가 구체 클래스에 의존하게 되면 DIP, OCP 원칙을 위반할 가능성이 크다.
  • 테스트하기가 번거롭다.
  • 내부 속성을 변경하거나 초기화가 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.

 

 결론적으로 유연성이 떨어지고, 단독으로 사용하면 객체 지향에 위반되는 사례가 많아 안티패턴으로 불리기도 한다. 이러한 싱글톤 패턴의 문제점을 해결하면서 객체 인스턴스를 싱글톤으로 유지하도록 관리하는 것이 있는데, 스프링 컨테이너가 그 역할을 맡는다.

댓글