-
스프링을 이용한 싱글톤 패턴스프링/스프링 기본이론 2023. 1. 30. 18:15
스프링에서의 싱글톤 컨테이너
@Test @DisplayName("스프링 컨테이너와 싱글톤") void springContainer() { ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class); MemberService memberService1 = ac.getBean("memberService", MemberService.class); MemberService memberService2 = ac.getBean("memberService", MemberService.class); // 참조값이 같은 것을 확인 System.out.println("memberService1 = " + memberService1); System.out.println("memberService2 = " + memberService2); // memberService == memberService2 assertThat(memberService1).isSameAs(memberService2); }
스프링 컨테이서에서 빈을 받아오는 getBean()으로 memberService.class의 memberService라는 빈의 인스턴스를 받아와서 참조값을 확인해본 결과 두 인스턴스의 참조값이 같은 것을 확인할 수 있었다. 위와같은 싱글톤 패턴을 하는 별도의 코드를 따로 작성하지 않아도 된다는 것이다.
스프링 컨테이너가 있으면 클라이언트의 요청이 올 때마다 새로운 객체를 생성하는 것이 아닌, 만들어진 객체를 다시 사용할 수 있다.
스프링 싱글톤 패턴의 주의점
싱글톤 패턴은 하나의 객체 인스턴스를 공유하기 때문에 유지하게 설계하면 안된다. 항상 무상태로 설계 되어야 한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 되도록 읽기만 허용해야 한다.
- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, TreadLocal 등을 사용해야 한다.
문제 발생 예시
class StatefulServiceTest { @Test void statefulServiceSingleTon() { ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class); StatefulService statefulService1 = ac.getBean(StatefulService.class); StatefulService statefulService2 = ac.getBean(StatefulService.class); // ThreadA : A 사용자가 10000원 주문 statefulService1.order("userA", 10000); // ThreadA : B 사용자가 20000원 주문 statefulService2.order("userB", 20000); // ThreadA : 사용자 A가 주문 금액 조회 int price = statefulService1.getPrice(); // 10000원이 아니라 20000원이 조회됨 -> 인스턴스가 똑같아서 System.out.println("price = " + price); Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000); } static class TestConfig { @Bean public StatefulService stateFulService() { return new StatefulService(); } } }
위의 테스트코드에서처럼 싱글톤 패턴은 인스턴스가 하나이기 때문에 중간에 다른 사용자가 인스턴스의 값을 변경하면 값이 달라질 수가 있다.
public class StatefulService { private int price; // 상태를 유지하는 필드 public void order(String name, int price) { System.out.println("name = " + name + ", price = " + price); this.price = price; // 여기가 문제! } public int getPrice() { return price; } }
문제점이 발생한 이유는 StatefulService라는 클래스에서 price를 변경해주었기 때문이다.
public class StatefulService { // private int price; // 상태를 유지하는 필드 public int order(String name, int price) { System.out.println("name = " + name + ", price = " + price); return price; } }
위에서 private int price라는 지역변수를 사용해서 값을 세팅해주었는데, 그렇게 하면 안되고 price를 바로 return 해줘야한다.
StatusfulService의 price 필드는 공유되는 필드인데 특정 클라이언트가 값을 변경하는 것이 문제였다. 이런 문제들이 실제로 터지면 복구하는 것도 오래걸리며 문제를 찾기 힘들기 때문에 처음에 싱글톤패턴을 잘 적용해야 한다.
공유필드는 조심해야하며 스프링 빈은 항상 무상태로 설계해야한다.
'스프링 > 스프링 기본이론' 카테고리의 다른 글
의존관계 자동 주입 (0) 2023.01.31 컴포넌트 스캔 (0) 2023.01.30 @Configuration과 싱글톤 패턴 (0) 2023.01.30 싱글톤 패턴의 개념 (0) 2023.01.28 의존관계 주입과 스프링 컨테이너 (0) 2023.01.24