Coderoad

싱글톤

2023-01-20 at Spring category

본 문서는 인프런에서 수강할 수 있는 스프링 핵심 원리 - 기본편을 수강한 후, 공부한 내용을 정리한 문서입니다. 본 문서의 모든 저작권은 해당 강의의 저자이신 김영한 우아한형제들 기술이사님께 있습니다.

Spring과 싱글톤

싱글톤 패턴은 클래스의 인스턴스딱 1개 생성되는 것을 보장하는 디자인 패턴입니다. 따라서 우리가 싱글톤 패턴을 구현할 때, 의도적으로 객체 인스턴스가 2개 이상 생성되지 않게 해야합니다. 제가 갑자기 싱글톤에 대해서 설명하는 이유는, 이 싱글톤 패턴이 Spring과 아주 밀접한 관계가 있기 때문입니다.

웹 애플리케이션은 Spring 애플리케이션의 절대 다수를 차지합니다. Spring 프레임워크가 당시 무겁고 복잡했던 기업용 Java 기술들을 순수하고 편리하게 제공하기 위해 탄생했다는 것을 생각하면 웹 애플리케이션과 Spring은 뗄레야 뗄 수 없는 사이입니다. 그런데 웹 애플리케이션은 다수의 사용자가 동시에 서비스에 접근하게 됩니다. 만약 웹 애플리케이션이 서비스에 접근하는 사용자 모두에게 새로운 서비스 인스턴스를 생성해서 제공한다면, 초당 1천 명만 접속해도 순식간에 1천 개가 넘는 객체가 생성되고 삭제되는 심각한 메모리 낭비가 발생합니다.

이러한 메모리 낭비를 막고자 싱글톤 패턴을 사용해 서비스 객체는 딱 1개만 생성되고 모든 사용자들이 이를 공유하도록 해야합니다. 싱글톤 패턴을 구현하는 방법은 다양합니다. 중요한 것은 애플리케이션이 실행 중인 동안 인스턴스가 1개만 생성되어야 한다는 것입니다. 성공적으로 싱글톤 패턴을 구현했다면, 메모리 낭비가 심해지는 문제를 쉽게 해결할 수 있습니다.

그러나, 이런 싱글톤 패턴에도 문제가 있습니다. 싱글톤 패턴을 직접 구현한다는 것은 결국 핵심 서비스 로직 외에도 추가적인 코드를 작성해야하는 비용이 드는 것이고, 의존 관계상 클라이언트가 구체 클래스(유일한 인스턴스)에 의존하게 됩니다. 이것은 DIP를 위반하게 되는 것으로 SOLID를 지키지 못한, 나쁜 객체지향 프로그래밍입니다. 또한 구체 클래스에 의존하게 됐기 때문에, OCP도 위반할 가능성이 높습니다.

메모리 낭비를 줄이겠다고 Spring의 근간부터 흔들리고 코드는 잔뜩 꼬여버리는 배보다 배꼽이 더 커져버린 상황이 발생합니다. Spring은 이런 문제를 해결하기 위해 Spring 컨테이너에게 객체 인스턴스를 싱글톤으로 관리하도록 했습니다.

싱글톤 컨테이너

Spring 컨테이너는 싱글톤 컨테이너의 역할을 담당합니다. 다시 말해서, Spring 컨테이너가 관리하는 Spring 빈들은 모두 싱글톤 패턴으로 관리되는 인스턴스들입니다. 우리가 Spring 컨테이너에 대해 정리했을때, Spring 빈으로 등록한 객체들은 Spring 컨테이너가 인스턴스화하여 가지고 있다가 요청에 따라 의존관계를 주입해준다는 것을 알았습니다.

이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리(Singleton Registry)라고 합니다. Spring 컨테이너의 이런 기능 덕분에 싱글톤 패턴을 직접 구현하면서 발생한 문제들을 해결하고도 인스턴스를 싱글톤으로 유지할 수 있습니다. 즉, 사용자의 요청이 애플리케이션으로 올 때 마다 인스턴스를 생성하는 것이 아니라 이미 만들어진 인스턴스(빈)을 공유해 효율적으로 재사용할 수 있게 된 것입니다.

물론, Spring의 빈 등록 방식은 다양합니다. 기본 방식이 싱글톤일 뿐, 다른 방식도 지정할 수 있습니다.

싱글톤 방식의 주의점

싱글톤 방식을 사용할 때 너무나도 중요하고 명심해야하는 점은 Spring 빈을 설계할 때 무상태(Stateless)로 설계해야 한다는 것입니다. 싱글톤으로 관리되는 Spring 빈을 공유하는 사용자들은 인스턴스의 공유 가능한 값(클래스 변수)들도 동시다발적으로 접근 가능합니다. 만약 어떤 값을 유지하고 이를 중요 서비스 로직에 사용하도록 빈을 설계했다면 치명적인 오류가 발생할 수 있습니다.

예를 들어, 클래스 변수에 내가 주문한 금액을 저장해두고(Stateful) 이를 통해 은행 계좌에서 주문 금액만큼 인출되도록 서비스 객체를 설계했다고 가정합시다. 이 서비스 객체가 싱글톤 방식으로 관리되고 인스턴스를 모든 사용자가 공유한다면 클래스 변수도 당연히 모든 사용자가 공유하는 변수입니다. 이 변수에 나의 주문 금액을 저장해두어도 다른 사용자가 몇 초 차이로 나중에 주문 서비스를 이용하면 값이 덮어씌워질 수 있다는 것입니다.

만약, 난 10만원만 사용했는데 3초 뒤에 다시 주문 서비스를 이용한 사람이 100만원을 사용했고 내가 5초 뒤에 주문을 확정지었다면 나도 모르는 새에 주문 금액은 100만원으로 늘어나는 대형 사고가 발생하는 것입니다. 때문에, 무슨 일이 있어도 싱글톤 방식을 사용할 때는 무조건 무상태(Stateless), 공유 가능한 필드가 없도록 Spring 빈을 설계해야합니다.

@Configuration

사실 Spring 컨테이너가 싱글톤 컨테이너의 역할을 할 수 있는 것은 @Configuration 어노테이션이 붙은 구성자, Java 설정 클래스 덕분입니다. 우리가 @Bean 어노테이션을 통해 Spring 빈으로 등록한 객체들은 @Configuration 어노테이션이 없으면 싱글톤이 보장되지 않습니다. 도대체 @Configuration에 무슨 비밀이 있길래 이 어노테이션만 붙이면 싱글톤이 보장되는 것일까요?

바로, @Configuration 어노테이션이 활용하는 CGLIB라는 기술에 있습니다. @Configuration 어노테이션은 CGLIB 기술을 통해 Java 설정 클래스에서 같은 객체를 여러번 new 키워드로 생성해도 처음 생성된 인스턴스를 활용하도록 바이트코드를 조작해 싱글톤을 보장합니다. 즉, 아무리 new 키워드를 여러번 사용해도 바이트코드 단계에서 new 키워드가 새로운 인스턴스를 만들지 않고 기존에 생성된 인스턴스를 반환하도록 합니다. 따라서 Spring 컨테이너가 싱글톤 방식을 사용하도록 하려면, Java 설정 클래스(구성자)에 @Configuration 어노테이션을 잊어선 안됩니다.

hangillee

Personal blog by hangillee.

Road to good developer.