Languege/Java & Spring / / 2022. 3. 14. 10:59

[김영한 스프링] 싱글톤 컨테이너

 

웹 애플리케이션과 싱글톤

웹 애플리케이션은 보통 여러 고객이 동시에 서버에 요청을 보냅니다. 스프링이 없는 순수한 DI 컨테이너memberService를 클라이언트 A,B,C가 요청한다고 쳤을 때, DI 컨테이너는 매번 다른 객체를 새롭게 생성하게 됩니다.

하지만 고객 트래픽이 초당 100개 1000개씩 생성된다면 메모리 낭비가 심해질 것 입니다. 이를 해결하기 위해서 gof디자인 패턴에서 나오는 싱글톤(Singleton) 이라는 패턴이 나왔는데, 하나의 static 객체를 메모리에 띄우고 이를 재사용하여 시스템의 효율을 높이는 방법입니다.

 


 

싱글톤 패턴의 문제점

순수 자바의 싱글톤 패턴은 아래와 같은 형식으로 사용이 가능합니다.

하지만 이 방법에는 여러가지 문제점이 있습니다.

  • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다. -> DIP 위반
  • 클라이언트가 구체 클래스에 의존해서 OCP를 위반할 가능성이 높다.
  • 테스트가 어렵다.
  • 내부 속성을 변경 혹은 초기화하기 어렵다.

결론 -> 유연성이 떨어진다.

 


스프링에서 싱글톤 패턴의 해결

싱글톤 패턴은 안티 패턴이라고 불리기도 합니다. 하지만 스프링 컨테이너에서는 싱글톤 패턴의 문제점을 해결하며, 객체 인스턴스를 싱글톤(1개만 생성)으로 관리합니다. 바로 지금까지 학습했던 스프링 Bean이 바로 싱글톤으로 관리되는 Bean 입니다.

 


 

싱글톤 컨테이너

스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도 Bean으로 등록된 객체의 instance를 싱글톤으로 관리해줍니다.

스프링 컨테이너는 싱글톤 컨테이너 역할을 합니다. 싱글톤 객체를 생성하고 관리하는 기능은 싱글톤 레지스트리라 부릅니다.

스프링 컨테이너의 이런 기능 덕분에 싱글톤 패턴의 모든 단점을 해결하며, 객체를 싱글톤으로 효율적으로 유지가 가능합니다.(DIP, OCP, 테스트, private 생성자로부터 자유로워짐)

하지만 스프링의 기본 Bean 등록 방식은 싱글톤이지만, 싱글톤 방식만 지원하는 것은 아닙니다. 요청할 때 마다 새로운 객체를 생성해서 반환하는 기능도 제공합니다.

 


 

싱글톤 방식의 주의점

싱글톤 패턴은 항상 주의해야할 점이 있습니다. 그건 바로 Stateful하게 설계를 하면 안된다는 점입니다.

무상태(stateless)로 설계해야 합니다.

  • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
  • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
  • 가급적 읽기만 가능해야 한다.
  • 필드 대신 자바에서 공유되지 않는 지역변수, parameter, ThreadLocal 등을 사용해야 한다.

 

그 이유는 다음과 같습니다.

[싱글톤 패턴을 stateful하게 설계하게 되는 경우]

사용자 A와 B가 아래 그림과 같이 물건을 주문한다고 가정합니다.

문제는 싱글톤 패턴이기에 사용자 A는 10000원의 물품을 주문했는데 실제 결제에서는 중간에 Thread B인 사용자 B가 20000원을 주문하여 service1.getprice()를 했을 때, 20000원이 조회되는 경우가 발생합니다. 즉 사용자 A는 자신이 주문한 금액이 아닌 금액만큼 결제가 될 가능성이 있다는 문제가 발생합니다.

 

그래서 다음과 같은 코드로 변경해야 합니다.

이런 문제를 방지하기 위해서 싱글톤 패턴이 적용이 된다면 stateless로 설계해야 큰 문제가 발생하지 않을 가능성이 높습니다. 스프링 빈은 항상 무상태(stateless)로 설계해야 합니다.

 


 

@Configuration 과 싱글톤 (@Configuration의 역할)

Configuration의 역할은 간단합니다. 싱글톤이 깨지지 않게 도와주는 스프링 컨테이너에서 중요한 역할을 합니다.

위의 AppConfig.java 파일에서 보기에는 memberRepository는 총 3번 실행되게 됩니다. 하지만 그렇게 된다면 new MemoryMemberRepository();를 총 3번 return 하게 되고, 이는 3개의 instance가 생성되는 것처럼 보입니다. 하지만 @Configuration을 통해서 그것을 방지하게 됩니다. 그러면 @Configuration이 실제로 그렇게 동작하는지 살펴보겠습니다.

[@Configuration 적용]

[@Configuration 미적용]

즉 위 이미지와 같이 Configuration 어노테이션을 미적용한다면 총 3번의 memberRepository가 call되어 싱글톤 패턴이 깨지게 되는 문제가 발생합니다. 그럼 어떻게 Configuration 어노테이션이 그런 역할을 하는걸까요? 바로 바이트코드 조작을 통해서입니다. @Configuration이 붙어있다면 바이크코드 조작된 AppConfig가 CGLIB 기술을 통해서 스프링 빈에 등록되지만, 아니라면 순수한 AppConfig가 등록되어 자바 코드대로 싱글톤을 보장하기 않게 됩니다.

 

[싱글톤을 보장하지 않는 순수한 AppConfig]

[CGLIB 기술을 통해 스프링 빈에 등록된 AppConfig]

위 이미지와 같이 CGLIB 기술을 이용하여 AppConfig@CGLIB$$ 형태로 변환되어 내부 로직을 이용해 싱글톤을 유지해줍니다. 김영한님 강의에서 예상 코드로 만약 memberRepository가 없다면 instance를 생성하여 return시켜주고, memberRepository가 있다면 해당 instance를 return하는 방식으로 간단하게 설명하였습니다.

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유