[Spring] 싱글톤 패턴, 싱글톤 컨테이너와 @Configuration

Big0·2022년 8월 14일
0

"본 게시글은 김영한님의 '스프링 핵심 원리 기본편'을 기반으로 작성된 글입니다."

Singleton pattern

'하나'의 인스턴스만 생성하여 사용하는 디자인 패턴. 즉 인스턴스가 필요할 때, 똑같은 인스턴스를 만들지 않고, 기존의 인스턴스를 활용하는 것이다.

기존의 문제점

한 사용자가 우리 서버에 요청을 한다고 가정해보자. 사용자는 회원 등록 서비스를 요청했고, 그 로직에 따라 'memberService'가 요청 될 것이다. 그러면 DI 컨테이너에서는 회원 등록 서비스를 수행하기 위해 새로운 객체를 생성할 것이다. 그렇다면 새로운 사용자가 나타나 똑같은 요청을 한다면? 또다시 새로운 사용자를 위한 객체 인스턴스가 생성될 것이며, 이는 N명의 사용자가 같은 요청을 N번 했다면, N개의 새로운 객체가 생성된다. 웹 애플리케이션은 새로운 고객이 계속 요청을 하기 때문에 이는 한정된 자원에 문제가 생기게 될 것이다.

다음과 같이 사용자 두 명이 각각의 서비스 요청을 했다고 해보자.

void pureContainer() {
		//설정 정보(스프링이 없는 순수한 DI컨테이너 AppConfig)
        AppConfig appConfig = new AppConfig();
		
        //사용자1 Service요청
        MemberService memberService1 = appConfig.memberService();

		//사용자2 Service요청
        MemberService memberService2 = appConfig.memberService();

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

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

아래는 위 코드를 실행한 결과값이다. 주소가 다른 것을 보면 각각의 서비스는 매 요청 마다 새로운 객체를 생성하고 있는 것을 확인할 수 있다. 이러한 사용자가 수백 수천 수만 명이 된다면? 계속 새로운 객체를 생성하고 있을 것이다. 메모리 낭비가 정말 심각할 것이다. 여기서 memberService 뿐만 아니라 다른 서비스도 같이 생성한다면 더더욱 심해질 것이다.

하지만! 여기서 싱글톤 패턴을 적용한다면 수많은 요청이 오더라도 하나의 인스턴스만 생성되기 때문에 이 문제를 해결할 수 있을 것이다...만. 이를 스프링 싱글톤 컨테이너 없이 구현하게 된다면 아래와 같은 문제점이 발생한다.

싱글톤 패턴의 문제점(싱글톤 컨테이너 사용X)

  • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다. 클라이언트가 구체 클래스에 의존하기 때문에 DIP, OCP 원칙을 위반한다.
  • 테스트하기 어렵다.
  • 내부 속성을 변경하거나 초기화 하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.
  • 결론적으로 유연성이 매우 떨어진다.

싱글톤 컨테이너의 역할

싱글톤 컨테이너를 사용함으로써 위에서 보았던 문제들을 모두 해결하며, 싱글톤의 목적이었던 각각의 요청 마다 새로운 객체가 생성되었던 문제까지 해결하는 모습을 볼 수 있다.

void springContainer() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        
        //AppConfig가 아닌 ApplicationContext에서 memberService를 가져옴
        //스프링 컨테이너에서 가져옴
        MemberService memberService1 = ac.getBean("memberService", MemberService.class);

        MemberService memberService2 = ac.getBean("memberService", MemberService.class);

        //참조값이 같은 것을 확인
        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);

        //memberService1 == memberService2
        Assertions.assertThat(memberService1).isSameAs(memberService2);
    }

0개의 댓글