Spring 을 공부하다가 Application Context 는 자바 빈을 싱글톤으로 관리한댜는 것을 배웠습니다.
싱글톤에 대해 더 알아보고자 포스팅을 제작하게 되었습니다 🔥
일반적으로 자바 클래스를 생성시 생성자를 통해 해당 클래스의 인스턴스를 생성하고 사용합니다.
일반적인 인스턴스 생성은 개발자가 원하는 만큼 인스턴스를 만들어 낼 수 있으며, 메모리상에서 각 인스턴스의 힙 공간을
할당받습니다.
해당 이미지를 보시면 생성자를 통해 두개의 인스턴스가 생성된 것을 볼 수 있습니다 🙆🏻
stack에 존재하는 참조 변수가 heap 영역에 생기는 인스턴스의 주소를 가리키고 있는 형태입니다.
인스턴스란 쉽게 이야기해서 클래스가 메모리에 생성된 상태를 의미합니다.
그리고 이렇게 생성된 인스턴스의 주소를 참조하는 참조변수를 통해서 인스턴스를 활용합니다.
자 이제, 싱글톤 패턴에 대해 알아보겠습니다.
싱글톤 패턴의 정의는 단순합니다.
싱글톤 패턴이란 메모리 영역에 오직 유일무이하게 존재하는 인스턴스를 생성하는 방법을 의미합니다.
여기서 유일무이하다는 것은 싱글톤 패턴을 따르는 클래스의 인스턴스는 "오직 하나"입니다.
해당 코드를 통해 싱글톤 패턴의 인스턴스를 생성하는 방법을 알아보겠습니다 💪
public class Singleton {
private static Singletone instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
생성자가 private 이기 때문에 클래스 외부에서는 절때 인스턴스를 생성할 수 없습니다.
그렇기 때문에 public 범위의 메소드 ( getInstance ) 를 통해 이미 생성된 싱글톤 패턴을 따르는
인스턴스를 가져옵니다 🧑🏼💻
자 그러면 위에 설계한 싱글톤 클래스를 이용해서 간단하게 테스트를 해보겠습니다 🧪
@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonServiceTest() {
SingleTon singleton01 = SingleTon.getInstance();
SingleTon singleton02 = SingleTon.getInstance();
Assertions.assertThat(singleton01).isEqualto(singleton02);
테스트 결과는 성공입니다.
SingleTon 클래스는 싱글톤 패턴을 따르기 때문에 singleton01 참조변수와 singleton02 참조변수가 가리키는
주소는 동일합니다.
assertThat() 은 동일성 검사를 하기 때문에 해당 테스트 코드는 성공입니다 👍
이 외에도 싱글톤 패턴을 사용하는 다양한 이유가 있는데 정리해보겠습니다.
이렇게 싱글톤 패턴 구현을 통해 다양한 이점을 얻을 수 있습니다.
그렇다면 문제점은 존재하지 않을까요?
아쉽게도 싱글톤 패턴은 여러가지 문제점 또한 야기할 수 있습니다 🥲 이제 싱글톤 패턴의 문제점에 대해 알아보겠습니다.
이처럼 싱글톤 패턴은 Trade-off 가 존재함으로 사용시 잘 고려해서 사용해야 합니다.
따라서, 반드시 싱글톤 패턴이 필요한 상황이 아니거나 활용에 있어 자신이 없다면 지양하는 것이 좋습니다 🔐
앞에서 살펴보았듯이 싱글톤 패턴은 여러가지 문제점을 가지고 있습니다.
그러나 스프링은 이러한 싱글톤 패턴의 문제점을 해결하면서 싱글톤 패턴을 사용합니다 😬
스프링 프레임워크에서 생성되는 자바빈(Bean)을 생성하고 관리하는 주체는 스프링 컨테이너입니다.
스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤 방식으로 관리합니다 ❗️
즉, 지금까지 스프링에서 사용했던 모든 빈들은 스프링 컨테이너에 의해 싱글톤 방식으로 관리되어 왔습니다.
이렇게 스프링 컨테이너가 자바 빈을 싱글톤 방식으로 관리하는 것을 '싱글톤 레지스트리'라고 합니다.
이를 확인하기 위해 간단한 테스트를 진행하겠습니다 🔥
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
ApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);
//1. 조회: 호출할 때 마다 같은 객체를 반환
MemberService memberService1 = ac.getBean("memberService",
MemberService.class);
//2. 조회: 호출할 때 마다 같은 객체를 반환
MemberService memberService2 = ac.getBean("memberService",
MemberService.class);
//참조값이 같은 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
//memberService1 == memberService2
assertThat(memberService1).isSameAs(memberService2); // true
}
해당 테스트를 통해 memberService 자바 빈은 모두 같은 주소의 인스턴스임을 확인할 수 있습니다 🧑🏼💻
스프링 컨테이너가 이렇게 싱글톤 패턴의 문제점을 해결하면서 싱글톤 방식으로 자바빈을 관리하지만, 개발자가 주의해야할 부분이 여전히 존재합니다.
스프링 빈은 항상 무상태(Stateless) 를 유지하도록 설계해야 합니다.
코드를 통해 문제점을 확인해보겠습니다.
class SingletonStatleFulTest() {
@Test
void stateFulServiceTest() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
// user1, user2 가 주문
statefulService1.takeOrder("user1", 10000);
statefulService2.takeOrder("user2", 20000);
int price = statefulService1.getPrice();
Assertions.assertThat(statefulService1.getPrice()).isEqualTo(statefulService2.getPrice());
이 코드의 실행 결과 user1 주문금액(10000원) 이 user2 사용자의 주문금액(20000원)으로 할당되어지는 오류가 발생합니다 🐞
멀티 쓰레드 환경에서 여러 쓰레드가 싱글톤에 동시 접근하는 경우 이런 오류가 발생할 수 있습니다.
이렇게 클라이언트가 특정값을 변경할 수 있게 하는 구조를 stateful 한 구조인데, 이런 결과를 방지하도록 주의깊은 설계가 필요합니다 👨💻
자바의 일반적인 싱글톤 패턴과 Spring에서의 싱글톤 패턴은 비슷하지만 구현 방법이 매우 다릅니다.
자바에서 싱글톤 패턴은 안티 패턴(지양)으로 여겨지지만, Spring 에서는 기본적으로 싱글톤 패턴으로 모든 자바빈을 관리합니다 👨💻
싱글톤 패턴을 공부하면서 Spring 에서 기본 개념과 원리를 익히는 것이 매우 중요하다는 것을 매번 느꼈습니다.
또한 Spring을 공부 하면서 Spring Framework 가 자바의 객체 지향적인 장점을 잘 살릴 수 있도록 설계되었다는 것을 느낍니다.