Singleton(싱글톤)

웹 애플리케이션은 보통 여러 고객이 동시에 요청을 하게 되는데, 아래와 같이 코드를 짠다면 고객이 요청을 할 때마다 새 객체가 생성된다.

public class SingletonTest {

    @Test
    @DisplayName("Pure DI Container")
    void pureContainer() {
        AppConfig appConfig = new AppConfig();

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

        System.out.println("memberService1 = "
                + memberService1); // memberService1 = study.corebasic.member.MemberServiceImpl@35fc6dc4
        System.out.println("memberService2 = "
                + memberService2); // memberService2 = study.corebasic.member.MemberServiceImpl@7fe8ea47

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

그렇게 되면 인스턴스가 무수히 많이 생성하게 되기 때문에 싱글톤 패턴을 적용하여 해결할 수 있다.

Java에서의 싱글톤 패턴

우선 Java에서 싱글톤 패턴을 구현하기 위해서는 다음과 같이 구현할 수 있다.

  • 클래스의 인스턴스가 한 개만 생성되는 것을 보장하는 디자인 패턴

  • 생성자를 private으로 선언하여 외부에서 new 키워드를 사용한 객체 생성을 막음

위 방법으로 순수 Java로 싱글톤 패턴을 구현할 수 있지만 여러 단점이 생겨나게 된다.

  • 싱글톤 패턴 구현 코드 증가

  • 의존관계상 클라이언트가 구체 클래스에 의존 -> DIP 위반

  • 클라이언트가 구체 클래스에 의존해서 OCP, 테스트, private 생성자로 자식 클래스 생성 불가 등의 문제점 발생

싱글톤 레지스트리(Singleton Registry)

스프링 컨테이너에 객체들이 빈으로 등록되면서 객체를 생성하고 관리하는데, 이 때 싱글톤 레지스트리 패턴을 사용한다.

  • 싱글톤 패턴을 위한 getInstance()와 같은 코드가 필요하지 않음

  • DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있음

때문에 스프링 컨테이너에 빈으로 등록되면 싱글톤으로 객체를 관리하게 되면서 위에 언급 된 문제점을 해결할 수 있다.

싱글톤 방식의 주의점

싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 무상태(stateless)로 설계해야 한다.

  • 특정 클라이언트에 의존적인 필드 존재 X

  • 특정 클라이언트가 값을 변경할 수 있는 필드 존재 X

  • 가급적 읽기만 가능하도록 설계

  • 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용

문제 발생 예시

  • 위 코드는 statefulService1statefulService2가 같은 price 공유

  • statefulService1price를 변경하면 statefulService2도 변경되는 문제 발생

@Configuration과 싱글톤

Config로 스프링 컨테이너에 등록되면 자바 코드 상으론 여러 번의 memberRepository() 호출을 하여 여러 개의 인스턴스를 생성하게된다.

proxyBeanMethods 옵션과 inter-bean 호출

하지만 실제로는 한 번만 호출되며, 스프링이 CGLIB라는 라이브러리를 통해 @Configuration이 붙은 클래스를 프록시 클래스를 만들어 싱글톤을 보장한다.

  • @Configuration의 기본값은 proxyBeanMethods=true. 동일 클래스 내 @Bean 메서드 호출을 프록시가 가로채어 컨테이너에 등록된 싱글톤을 반환함

  • proxyBeanMethods=false로 설정하면 메서드 간 직접 호출이 일반 자바 호출이 되어 새로운 인스턴스가 생성될 수 있음

참고자료

Last updated

Was this helpful?