Study Record/Spring Core

[Lecture-Core] Singleton

고고잉93 2024. 4. 27. 02:04
728x90

순수 DI컨테이너(AppConfig)에서는 요청마다 객체를 생성한다.

→ 100건의 요청마다 100개의 객체를 생성 → 낭비 발생

   ★해결책: 객체가 1개만 생성되고 공유하도록 설계한다 → 싱글톤(Singleton)패턴

// 싱글톤 컨테이너 사용
ApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);

 //1. 조회: 호출할 때 마다 같은 객체를 반환
 MemberService memberService1 = ac.getBean("memberService", 
MemberService.class);

 //2. 조회: 호출할 때 마다 같은 객체를 반환
 MemberService memberService2 = ac.getBean("memberService", 
MemberService.class);

assertThat(memberService1).isSameAs(memberService2); // True

싱글톤 컨테이너를 사용하면 요청마다 객체를 생성하지 않고 만들어진 객체를 공유하여 효율적으로 재사용한다.

 

※ 싱글톤 패턴 사용시 주의점(Stateful ↔ Stateless)

싱글톤은 stateful(유지)가 아닌 Stateless(무상태)로 설계 하여야 한다.

public class Stateful {
 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;
 }
}
public class StatefulServiceTest {
 @Test
 void statefulServiceSingleton() {
 ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
 StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
 StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);
 
 //ThreadA: A사용자 10000원 주문
 statefulService1.order("userA", 10000);
 
 //ThreadB: B사용자 20000원 주문
 statefulService2.order("userB", 20000);
 
 //ThreadA: 사용자A 주문 금액 조회
 int price = statefulService1.getPrice();
 
 //Price값을 가장 이후에 주문한 price로 덮어버린다.
 System.out.println("price = " + price);
 Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
 }
 
 static class TestConfig {
 @Bean
 public StatefulService statefulService() {
 return new StatefulService();
 }
 }

 

Stateful 클래스에서 필드에 price를 두어 값이 공유가 되어버린다.

                                                                                   ★해결책  → 공유 되지 않는 지역변수, 파라미터를 사용해야 한다.

 

◆ @Configuration

@Configuration
public class AppConfig {

 @Bean
 public MemberService memberService() {
 System.out.println("call AppConfig.memberService");
 return new MemberServiceImpl(memberRepository());
 }
 
 @Bean
 public OrderService orderService() {
 System.out.println("call AppConfig.orderService");
 return new OrderServiceImpl(memberRepository());
 }
 
 @Bean
 public MemberRepository memberRepository() {
 System.out.println("call AppConfig.memberRepository");
 return new MemoryMemberRepository();
 }
public class ConfigurationSingletonTest {
 @Test
 void configurationTest() {
 ApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);

//memberService 1회 호출, memberRepository 1회 호출
 MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
 
 // orderService 1회 호출, memberRepository 2회 호출
 OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
 
 // memberRepository 3회호출 ???
 MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
 
 
 System.out.println("memberService -> memberRepository = " +
                memberService.getMemberRepository());
                
 System.out.println("orderService -> memberRepository = " +
                orderService.getMemberRepository());
                
 System.out.println("memberRepository = " + memberRepository);

 

Test를 실행해보자!

 

출력결과 모두 1번씩만 호출되었고, memberService와 orderService에서 호출했던 memberRepository도 같았다.

 

왜 3번의 호출이 1번씩만 호출되는 걸까?

@Test
void configurationDeep() {
 ApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class); //AppConfig도 스프링 빈으로 등록
 AppConfig bean = ac.getBean(AppConfig.class);
 
 System.out.println("bean = " + bean.getClass());
}

AnnotationConfigApplicationContext(AppConfig.class)로 값을 넘길시에 AppConfig도 스프링 빈에 등록된다.

클래스 정보에서 CGLIB가 붙어있는데 스프링이 라이브러리를 사용하여 AppConfig를 상속받은 어떠한 클래스를 만들고

스프링 빈에 등록한다. 그리고 그 클래스가 싱글톤이 보장되도록 한다.

→ 스프링 컨테이너에 존재시 찾아서 반환, 없을시 컨테이너에 등록하고 반환하는 동작을 한다.

 

 


스프링은 애초에 온라인 서비스 기술을 지원하기위해 생겨났다. 웹 애플리케이션은 여러개의 Client들이 동시에 그리고 수없이 요청을 한다. 이를 효율적으로 처리하기 위한 싱글톤(Singleton)패턴에 대해 하나씩 살펴보았다.

 

1. 싱글톤의 동작을 get.bean을 통해 호출하여 직접 확인해보았으며

2. 필드변수 사용을 하지않는 주의점에 유의(Stateful → Stateless)

3. @Configuration의 역할까지 알아 보았다.

 

https://github.com/Kwon9302/core/blob/main/src/test/java/hello/core/singleton/ConfigurationSingletonTest.java

 

 

728x90