[Spring] 05-1. 싱글톤 컨테이너

지찬우·2023년 1월 6일
0

Spring

목록 보기
16/27
post-thumbnail

이 시리즈는 인프런 강의(김영한 님의 ‘스프링 핵심 원리 - 기본편’)로 공부하며 혼자 기록하고, 사람들과도 공유할 수 있도록 작성하는 글이다. 최대한 추가적인 정보는 공식 홈페이지, 문서를 보며 얻을 예정이다.
(개인적인 생각과 이해가 들어가 있기 때문에 저의 ‘무식함’이 있을 수 있습니다😜 혹시라도 이 글을 보게 되시는 분이 계시다면 잘못된 부분 댓글로 많이 알려주시면 너무 감사하겠습니다!!)

GitHub Repository : https://github.com/jcw1031/spring-core-study


웹 애플리케이션과 싱글톤

대부분의 스프링 애플리케이션은 웹 애플리케이션이다.(물론 웹이 아닌 애플리케이션도 개발 가능) 웹 애플리케이션은 보통 여러 고객이 동시에 요청을 보낸다.

여러 개의 클라이언트가 memberService의 기능을 사용해야 하는 경우, DI 컨테이너(AppConfig)에서 new MemberService()를 호출해 여러 개의 객체를 생성하여 클라이언트에게 반환하게 된다.

객체를 생성하는 비용은 크기 때문에 효율적이지 않고, 메모리 낭비도 심하다. DI 컨테이너에서 객체가 매번 새로 생성되는지 간단하게 테스트해 보자.

스프링 없는 순수한 DI 컨테이너 테스트 🧪

test/java/Group이름.core 패키지에 singleton 패키지를 만들고 그 안에 SingletonTest 클래스를 만들었다.


AppConfig 객체를 생성하고 memberService() 메서드를 두 번 호출하여 memberService1, 2가 참조하도록 하였다.


DI 컨테이너에 여러 번 요청하면 각자 다른 객체가 생성되어 반환된다고 했다. 그럼 memberService1memberService2는 같지 않아야 한다. AssertionsassertThat()isNotSameAs()를 사용해 두 객체가 다른지를 테스트한다.

테스트를 실행해 보면 성공하는 것을 확인할 수 있다.

우리가 만들었던 스프링 없는 순수한 DI 컨테이너인 AppConfig는 요청할 때마다 객체를 새로 생성한다. 초당 100개의 요청이 있으면 초당 100개의 객체가 생성되어야 하는 것이다. 이 문제에 대한 해결 방안은 ‘싱글톤 패턴’이다.


싱글톤 패턴 📌

앞서 살펴봤던 문제를 싱글톤 디자인 패턴을 통해 해결해 보자.

싱글톤 디자인 패턴이란❓

싱글톤 디자인 패턴은 클래스 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다. private 생성자를 사용해 외부에서 임의로 new 키워드를 사용해 객체를 생성하지 못하게 막는다. 이렇게 1개만 생성된 객체를 공유하도록 하면 문제를 해결할 수 있다.

싱글톤 패턴 코드 ⌨️

싱글톤 패턴을 적용한 예제 코드를 작성해 보자.

앞서 만든 singleton 패키지에 SingletonService 클래스를 추가한다.


static 영역에 SingletonService 객체를 딱 하나만 생성해둔다.


이 객체 인스턴스가 필요하면 오직 getInstance() 메서드를 통해서만 조회가 가능하다. 항상 같은 객체를 반환한다.


단 하나의 객체 인스턴스만 존재해야 하기 때문에 생성자를 private로 막아 외부에서 new 키워드를 통해 객체를 생성하는 것을 방지한다.


다른 클래스에서 SingletonServicenew 키워드를 통해 생성하려 하는 경우 아래처럼 private 생성자이기 때문에 생성할 수 없다고 경고 문구가 뜨는 것을 확인할 수 있다.

그럼 그동안 MemberServiceOrderService, Repository 모두 싱글톤 패턴으로 변경하고 getInstance() 메서드로 반환하도록 수정해야 할까?
그럴 필요가 없다. 왜냐하면 “스프링 컨테이너를 사용하면 기본적으로 객체를 싱글톤으로 만들어 관리해 주기 때문이다!”

싱글톤 패턴 테스트 🧪

싱글톤 패턴인 MemberService를 테스트해 보자.

SingletonTest에 테스트 코드를 작성한다. 두 개의 SingletonService 인스턴스가 getInstance()를 통해 반환되는 객체를 참조한다. 그리고 두 객체의 참조가 같은지 확인하는 isSameAs() 메서드를 통해 둘을 비교한다. 실행해 보면 성공하는 것을 볼 수 있다.

isEqualTo()isSameAs()의 차이점이 궁금해져서 검색을 좀 해봤다. isEqualTo()는 ‘값’이 같은지를 비교하는 것이고, isSameAs()는 ‘참조’가 같은지 비교하는 메서드이다.

문제점 ‼️

싱글톤 패턴은 이미 만들어진 객체를 공유해서 효율적으로 사용할 수 있지만, 문제점도 존재한다.

  • 싱글톤 패턴 구현 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존해야 한다. → DIP를 위반하고, OCP를 위반할 가능성이 높아진다.
  • 테스트가 어렵다.
  • 내부 속성을 변경하거나 초기화하기 어렵다.
  • private 생성자로 인해 자식 클래스를 만들기 어려워진다.
  • 유연성이 떨어져 DI가 어려워진다.

참고 : 이러한 이유들로 싱글톤 패턴은 안티 패턴이라고 불리기도 한다.


싱글톤 컨테이너 😄

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다. 스프링 빈이 싱글톤으로 관리되는 빈이다.

스프링 컨테이너와 싱글톤

컨테이너 생성 과정을 살펴보면, 객체를 하나만 생성해 관리하는 것을 알 수 있다. 구성 정보 클래스를 기반으로 객체를 하나씩만 생성해 저장소에 저장하는 것을 확인할 수 있다.

싱글톤 객체를 생성하고 관리하는 기능을 ‘싱글톤 레지스트리’라고 한다.

스프링 컨테이너는 싱글톤 패턴의 코드들이 필요 없고 DIP와 OCP, 테스트, private 생성자로부터 자유롭게 싱글톤을 사용할 수 있다.

싱글톤 컨테이너 테스트 🧪

SingletonTest 클래스에 테스트 코드를 작성한다. 스프링 컨테이너를 생성하고, 두 개의 MemberService 인스턴스가 모두 getBean() 메서드로 반환된 객체를 참조하도록 한다. 그리고 isSameAs() 메서드를 통해 두 객체가 같은지 확인해 보면 싱글톤인 것을 알 수 있다.


아래 그림처럼 하나의 객체만 생성하여 여러 클라이언트에게 공유하며 효율적으로 사용하는 것이다.

스프링의 기본 빈 등록 방식은 싱글톤이지만 요청이 들어올 때마다 새로운 객체를 생성해 반환하는 기능도 제공한다. 이 내용은 빈 스코프에서 설명한다.


스프링 컨테이너를 사용하면 코드가 오히려 복잡해지는 것 같지만 장점이 굉장히 많다고 했었다. 그 장점들 중 하나가 바로 이 싱글톤 컨테이너이다.

다음 시간에는 싱글톤 방식의 주의점에 대해 공부해 보자.

profile
좋은 개발자가 되자.

0개의 댓글