Spring Framework

스프링과 싱글톤(Singleton)

석이 2021. 11. 20. 18:35

일러두기

이 글은 자바 국비지원 수업을 듣는 학생이 공부한 내용을 정리하기 위해 기록한 글임을 밝힙니다. 학생이 쓴 글이기 때문에 내용에 다소간의 오류가 있을 수 있습니다!

 

+ 이 글은 김영한님의 '스프링 핵심 원리 기초편' 수업을 듣고 정리한 내용입니다.

 

 

 

시작하기 앞서 : 한 줄 요약

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하고 싱글톤 패턴의 장점만 활용할 수 있도록 지원해준다. (대충 스프링이 짱이라는 이야기)

 

 

 

싱글톤이란?

싱글톤은 흔히 객체 지향 디자인 패턴으로 언급되는 용어로, 클래스의 인스턴스가 중복 생성되지 않게 보장해주는 패턴을 의미한다. 즉, 싱글톤(Singleton)이라는 단어에서 유추할 수 있듯 클래스의 인스턴스를 딱 1개만 두고 2개 이상은 허용하지 않는 것. 이러한 패턴은 웹 어플리케이션을 개발할 때, 클라이언트로부터 수시로 조회되는 객체에 사용되기에 매우 용이하다. 

 

한번 생각해보자. 당신이 만든 웹 어플리케이션에는 하루에 클라이언트로부터 대략 만 번 정도 조회되는 클래스가 있다(실제로 운용되는 웹서비스에는 만 번보다 훨씬 더 많이 조회되는 클래스들이 넘쳐날 것이다). 이 클래스는 조회될 때마다 인스턴스틀 생성하는 클래스이기 때문에 하루에 만 번씩, 인스턴스가 생성된다.

 

'이 기능은 고객들로부터 수시로 조회되는 기능이기는 하지만, 이 정도의 컴퓨터 자원이 매일매일 투입되기에는 그렇게까지 중요한 로직은 아닌 것 같은데.. 컴퓨팅 자원을 절약하면서 수시로 조회되는 부분도 지킬 수 있는 방법이 뭘까..?' 하고 당신은 생각한다. 잠시 생각을 거듭하던 당신은 이내 이러한 문제를 해결하기 위해 객체 지향 디자인 패턴 중 하나인 싱글톤 패턴을 생각해낸다. 그리고 하던 일을 멈추고 등을 기대고 앉아 이 기능에 싱글톤을 사용했을 때 얻는 이점과 문제점의 크기를 비교하기 시작한다.

 

우리도 한번 싱글톤의 이점과 문제점을 생각해보자.

 

싱글톤의 이점

  1. 싱글톤은 클라이언트의 요청이 올 때마다 새로 인스턴스를 생성하지 않고, 미리 만들어진 객체를 공유한다.
  2. 이는 다수의 트래픽이 몰리는 기능에서 한정된 컴퓨팅 자원을 절약할 수 있는 이상적인 방법이다.
  3. 즉, 싱글톤 패턴은 한정적인 컴퓨팅 자원을 효율적으로 사용하도록 도와준다!

 

 

싱글톤의 문제점

위의 이점에서 살펴본 매력적인 장점과는 달리, 싱글톤 패턴에는 아래와 같은 수 많은 문제점이 있다.

  • 싱글톤 패턴을 구현하는 로직을 코드단에 추가 해 주어야 한다. (싱글톤 패턴을 구현하며 사용되는 private 생성자는 코드적인 측면에서는 조금 난해하고 (개인적으로) 보기 좋지 않은 모습이 있다.)
  • 의존관계상 클라이언트가 구체적인 클래스에 의존할 수 밖에 없다. (클래스를 공유 자원으로 두고 사용하는 것이기 때문에 '추상적인 것에 의존해야 한다'는 DIP 원칙을 위반하게 된다.)
  • 클라이언트가 구체적인 클래스에 의존하기 때문에 OCP 원칙을 위반할 가능성이 높다.
  • 테스트하기 어렵다. (의존관계가 깔끔하지 않기 때문)
  • 내부 속성을 변경하거나 초기화하기 어렵다. (인스턴스를 추가적으로 생성하지 못하게 하기 위해 싱글톤 패턴의 클래스는 private, static, final 등의 처리를 하게 되는데 이로 인해 클래스는 매우 경직된 성향을 띄게된다.)
  • 결론적으로 유연성이 떨어진다.

위와 같은 이유들로 싱글톤 패턴은 안티패턴으로 불리기도 한다.

 

 

 

 

스프링 써

당신은 의자에 등을 기대고 앉아 잠시 생각을 거듭한다. 싱글톤 패턴은 참 매력적이기는 하지만 클래스의 유연성이 떨어지고, 무엇보다 객체 지향 설계의 5대 원리인 SOLID 중 OCP와 DIP를 위반한다는 치명적인 약점을 가지고 있다. 그 때 옆을 지나던 (당신의 마음을 읽은) 동료가 당신의 어깨를 툭툭 치더니 한마디 한다.

 

"스프링 써"

 

"뭐?"

 

"스프링 쓰라고. 스프링이 다 해결해줄거야"

 

 

 

 

스프링 컨테이너

싱글톤은 참 매력적인 디자인 패턴이다. 그러나 위에서 언급한 수많은 문제점 때문에 사용하기에 부담이 되기도 한다. 싱글톤의 이점은 얻으면서 싱글톤 안에 내재한 문제점들은 제거한 상태로 사용할 수 있는 방법은 없을까? 있다, 바로 스프링 안에 내장된 스프링 컨테이너를 사용하는 것이다.

 

스프링 컨테이너는 스프링의 핵심 기능 중 하나로, 싱글톤 패턴의 문제점을 해결하면서 객체 인스턴스를 싱글톤으로 관리한다. 심지어는 코드 내에 싱글톤을 구현하는 코드를 적지 않아도 클래스를 싱글톤으로 두어 자동으로 관리하게 되는데, 이를 통해 private, static, final등을 적음으로서 발생하는 많은 문제점들을 해결할 수 있다. 다만 싱글톤을 사용하는 부분에는 그것이 일반적인 경우에서의 사용이든, 스프링을 통한 싱글톤 사용이든 상관 없이 반드시 유의해야 할 주의사항이 존재하는데 이는 아래와 같다.

 

 

싱글톤 사용의 주의점

  • 싱글톤은 객체를 하나만 생성해 공유한다.
  • 따라서 싱글톤 객체는 상태를 유지(stateful)하게 설계해서는 안된다. (객체를 클라이언트들이 공유하기 때문에 stateful하게 설계할 경우 A 고객의 사적인 정보가 다른 고객에게 보여질 수 있다. 이러한 설계로 고객의 개인정보가 유출이라도 될 경우, 고객 개인적인 입장, 기업적 입장 모두에서 상당한 피해가 발생할 수 있다!)
  • 따라서 반드시 무상태(stateless)로 설계해야 한다!!!

따라서, 클라이언트와 직접적으로 닿아 있는 클래스를 싱글톤으로 구현할 때에는 필드 대신 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용하도록 하자. 

 

 

 

스프링에서의 싱글톤. 컨테이너 등록은 어떻게?

(스프링 컨테이너에 객체를 싱글톤으로 저장하는 방법은 크게 두 가지이다. 객체가 '자동'으로 등록되는 컴포넌트 스캔과 객체를 '수동'으로 등록시키는 @Configulation 애노테이션 활용. 아래의 예시는 그 중 두 번째의 방법을 보여주는 예시이다.)

2021. 12. 02

 

애노테이션 중 @Configulation을 사용하면 된다. 아래의 예시는 김영한님의 '스프링 핵심 원리 기초편'에 나오는 예시로 자바 클래스에 @Configulation과 @Bean을 사용하여 어떻게 컨테이너에 객체를 등록할 수 있는지 보여준다.

 

@Configuration
public class AppConfig {

   @Bean
   public MemberService memberService() {
  	 return new MemberServiceImpl(memberRepository());
   }
   
   @Bean
   public OrderService orderService() {
     return new OrderServiceImpl(
       memberRepository(),
       discountPolicy());
   }
   
   @Bean
   public MemberRepository memberRepository() {
 	  return new MemoryMemberRepository();
   }
   
   ...
}

 

 

 

바이트코드 조작은 눈보다 빠르다 : @Configuration과 CGLIB 기술

마지막으로 어떻게 스프링이 스프링 컨테이너에 등록된 객체를 싱글톤으로 관리하는지 알아보자. 이 부분에는 CGLIB이라고 하는 기술이 사용되는데, 이 기술은 @Configuration 애노테이션이 적용된 클래스를 (개발자 몰래, 은밀히) 스프링이 조작하는데 사용된다. 위의 코드처럼 사용자가 작성해놓은 원본 AppConfig 클래스는 스프링에 반영되는 실제 클래스가 아니다. 스프링은 컨테이너에 설정정보를 반영하기 전 AppConfig의 자식클래스 AppConfig@CBLIB 클래스를 생성해 원본 클래스 대신 사용한다. 바로 이 지점에서 싱글톤의 마법이 일어나는 것인데 어떻게 스프링이 AppConfig의 바이트 코드를 조작하는지 아래의 코드를 통해 살펴보자. 

 

그 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다. 아마도 다음과 같이 바이트 코드를 조작해서 작성되어 있을 것이다. (실제로는 CGLIB의 내부 기술을 사용하는데 매우 복잡하다.)
- 김영한님의 '스프링 핵심 원리 기초편'

 

@Bean
public MemberRepository memberRepository() {
 
   if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
		return 스프링 컨테이너에서 찾아서 반환;
   } else { //스프링 컨테이너에 없으면
		기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
		return 반환
   }
}

 

 

 

이상으로 싱글톤 패턴과 스프링에서의 싱글톤 패턴 사용의 예를 살펴보았다. 싱글톤의 이점과 문제점을 살펴봤고, 싱글톤의 이점은 사용하면서 문제점을 완전히 제거할 수 있는 방법을 살펴보았다. 결론은... 스프링이 짱이라는 것. 공부를 할 수록 참 대단한 프레임워크인 것 같다는 생각이 든다.

 

ps. 공부를 통해 얻은 새로운 의문점 : node.js 같은 프레임워크도 스프링같은 기능들을 다 지원할까???