본문 바로가기
Spring/Spring Framework(Basic)

[Spring] Spring 을 이용하여 Service 구축하기 - 2

by 완두완두콩 2021. 10. 28.

싱글톤 패턴


디자인 패턴에는 여러가지가 있으나 앞으로의 서비스 개발에는 싱글톤 패턴을 이용할 것이다.

싱글톤 패턴을 이용하는 이유에는 효율성 문제이다. 싱글톤 패턴을 사용하지 않고 다른 패턴을 사용한다면 서비스가 요청될 때마다 객체를 생성하고 소멸하는 과정이 반복해서 발생하게 된다. 적은 수의 사용자라면 큰 문제가 없겠지만 서비스의 규모가 커지게 된다면 트래픽이 어마어마하게 많아질 것이고 메모리 낭비로 이어지게 된다.

 

-> 이를 해결하기 위해 싱글톤 패턴을 도입하여 하나의 객체만을 생성하고 공유되도록 설계하면 된다.

 

-> 주의할 점은 여러 클라이언트가 하나의 객체를 공유하기 때문에 전역 변수나 public으로 열어두는 등의 행동을 한다면 A라는 사람이 구매한 정보가 B에게도 보인다거나 하는 큰 장애가 발생할 수 있다. 앞으로의 내용에서 같이 알아가 보도록 한다.

 

-> 싱글톤에도 여러 단점이 있으나 왜 싱글톤인가 ? 이는 스프링 프레임워크 때문에 가능한 일이다.

스프링 컨테이너는 싱글톤 패턴을 따로 적용하지 않아도 알아서 싱글톤으로 관리해준다. 또한 싱글톤 유지를 위한 여러 코드를 작성할 필요도 없고 , DIP, OCP 도 모두 지키며 사용할 수 있기에 싱글톤을 사용한다.

 

1
2
3
4
5
6
7
8
9
10
    @Test
    void singletonTest (){
    MemberService memberService1 = ac.getBean("memberService", MemberService.class);
    MemberService memberService2 = ac.getBean("memberService", MemberService.class);
 
        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);
 
        Assertions.assertThat(memberService1).isSameAs(memberService2);
    }
cs

 

-> 테스트 결과 성공. 같은 객체임을 확인할 수 있었다.

 

 

싱글톤 방식의 주의점


 

- 무상태(stateless)로 설계해야 한다.

- 특정 클라이언트에 의존적인 필드가 있으면 안된다.

- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다

- 가급적 읽기만 가능해야 한다.

- 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package hello.core.singletonService;
 
 
 
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;
    }
}
 
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
  @Test
    void statefulTest() {
        //given
        StatefulService bean1 = ac.getBean(StatefulService.class);
        StatefulService bean2 = ac.getBean(StatefulService.class);
        //when
        bean1.order("userA"10000);
        bean2.order("userB"20000);
        //then
        int price = bean1.getPrice();
        Assertions.assertThat(price).isEqualTo(10000);
    }
cs

 

분명 userA가 주문한 금액은 10000원 이였으나 bean1.getPrice() 의 결과는 20000 으로 나와있다.

이는 싱글톤 패턴의 문제점이며 int price를 공유필드로 설정해놓았기 때문이다.

 

그렇다면 DI 컨테이너인 AppConfig는 과연 싱글톤 패턴이 잘 적용되는 것일까?

singletonTest 에서 봤듯이 적용은 잘 되는것 같은데 어떤 원리로 이렇게 되는것일까?

 

AppConfig@CGLIB


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
public class AppConfig {
 
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
 
    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(
                memberRepository(),
                discountPolicy());
    }
 
    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
 
    @Bean
    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}
 
cs

 

AppConfig의 memberRepository 영역을 보자.

 @Bean

    public MemberRepository memberRepository(){

        return new MemoryMemberRepository();

    }

해당 MemberRepository 객체는 총 두번 호출이 된다. MemberService 객체와 OrderService 객체 모두 두번 new

키워드를 통해 새로운 객체가 만들어지는데 어떻게 싱글톤 패턴이 적용되는 것일까 ? 라는 당연한 물음이 생겨야 한다.

이는 스프링이 'CGLIB' 이라는 바이트 코드 조작 라이브러리를 사용해서 AppConfig 를 상속받은 AppConfig@CGLIB

클래스를 생성하고 그 클래스를 이용해서 스프링 빈으로 등록한 것이다.

즉 , AppConfig@CGLIB 이 싱글톤을 보장되도록 해준다고 인지하면 될 것이다.

 

※ 주의점 : @Configuration 없이 @Bean 만을 통해서 스프링 빈에 등록하면 등록은 되지만 싱글톤은 유지되지 않는다.스프링 설정정보는 항상 @Configuration 을 사용하자.

 

 

-다음 장에서는 컴포넌트 스캔에 대해 알아본다.

 

* 해당 글은 'Infleran'의 김영한 강사님의 자료를 참조하였습니다.

댓글