본문 바로가기

프로그래밍/Spring

[Spring] 싱글톤 패턴(Singleton pattern)

싱글톤 패턴이란

 싱글톤 패턴을 따르는 클래스는 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출 된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 이와 같은 디자인 유형을 싱글톤 패턴이라고 한다.

 즉, 여러 사용자가 매번 객체를 호출할 때마다 객체를 생성하는 것이 아닌 최초에 생성된 객체 하나를 재사용 하는 것을 의미한다.

 

싱글톤 패턴 사용 이유

 같은 클래스의 객체를 만들어 사용하게 될 경우, 매번 새로운 객체를 만들게 되면 메모리의 낭비가 심하기 때문에 이를 방지하고자 사용한다.

 

예를들어,

순수한 DI 컨테이너

@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainer() {
	AppConfig appConfig = new AppConfig();
    
    // 1. 호출 할 때 마다 다른 객체를 생성한다.
    MemberService memberService1 = appConfig.memberService();
    
    // 2. 호출 할 때 마다 다른 객체를 생성한다.
    MemberService memberService2 = appConfig.memberService();
    
    Assertions.assertTaht(memberService1).isNotSameAs(memberService2);

주석 1. 객체를 생성하게 되면 매번 새로운 객체가 생성된다.

주석 2. 이런 경우 많은 객체를 생성해야 하는 서비스에서는 메모리 낭비가 심해진다.

 

위와같은 방법으로 객체를 생성하게 되면 매번 새로운 객체를 생성하기 때문에 메모리 낭비가 심하다.

 

싱글톤 패턴으로 구현

// 1. static 영역에 객체를 딱 1개만 생성한다.
private static final SingletonService instance = new SingletonService();

// 2. static method를 통해서만 객체를 생성하도록 한다.
public static SingletonService getInstance() {
	return instance;
}

// 3. private 생성자를 통해서 외부에서 new로 객체생성하는 것을 막는다.
private SingletonService(){ }
@Test
@DisplayName("싱글톤 패턴을 적용한 객체 생성")
void singletonContainer() {
    SingletonService singletonService1 = SingletonService.getInstance();
    SingletonService singletonService2 = SingletonService.getInstance();
    Assertions.assertThat(singletonService1).isSameAs(singletonService2);
}

주석 1. static 객체를 통해서 해당 객체를 1개만 생성할 수 있도록 지정한다.

주석 2. static 메소드를 통해서 외부에서 생성할 수 있도록 제한한다.

주석 3. new 연산자를 통해서 객체를 만드는 것을 private 생성자를 통해서 제한한다.

 

위와같이 싱글톤 패턴으로 객체를 구현함으로서 메모리 낭비를 방지할 수 있다.

 

싱글톤 패턴의 문제점

 1. 코드의 수가 길어진다.

 2. 테스트하기가 힘들어진다.

 3. 내부 속성을 변경하거나 초기화 하기 어려워진다.

 4. private 생성자로 자식 클래스를 만들기 어렵다.

 ... 등 한마디로 싱글톤 패턴유연성이 떨어진다고 할 수 있다.

 

하지만 스프링에서는 스프링 컨테이너가 이러한 단점들을 모두 해결해준다.

 

스프링의 싱글톤 패턴

 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리하기 때문에, 싱글톤 패턴의 모든 단점을 해결하고 객체를 싱글톤으로 유지할 수 있게된다.

 

@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
	AnnotationConfigApplicationContext appConfig = new AnnotationConfigApplicationContext(AppConfig.class);
    MemberService memberService1 = appConfig.getBean(MemberService.class);
    MemberService memberService2 = appConfig.getBean(MemberService.class);
    
    System.out.println("memberService1 = " + memberService1);
    System.out.println("memberService2 = " + memberService2);
    
    Assertions.assertThat(memberService1).isSameAs(memberService2);
 }

위 코드의 출력을 확인해 보면 객체를 새롭게 생성하지만, 같은 객체를 생성한 결과를 볼 수 있다.

 

싱글톤 방식의 주의점

 싱글톤 패턴을 사용하게 되는 경우 객체 인스턴스를 공유하기 때문에 객체 상태를 유지(stateful)하게 설계하면 안된다.

 

코드 예제

public class SingletonTest {

	@Test
    @DisplayName("싱글톤을 활용한 객체 생성")
    void notSingleton() {
    	Menu menu1 = Menu.getInstance();
        Menu menu2 = Menu.getInstance();
        
        menu1.setName("자장면");
        menu2.setName("볶음밥");
        
        System.out.println("menu1 = " + menu1.getName());
        System.out.println("menu2 = " + menu2.getName());
        
        Assertions.assertThat(menu1).isEqualTo(menu2);
     }
}

// 결과
// menu1 = 볶음밥
// menu2 = 볶음밥

 싱글톤 패턴을 사용할 경우 같은 객체를 사용하기 때문에 menu1에서 지정한 자장면이 menu2에서 지정한 볶음밥으로 바뀐 것을 확인할 수 있다.

 

 이러한 실수를 방지하기 위해서 무결성하게 코드를 작성하여야 한다.

public class Menu {
	private String name;
    private static final Menu instance = new Menu();
    
    private Menu(){ }
    
    public static Menu getInstance() {
    	retunr instance;
    }
    
    public String getName() {
    	return name;
    }
    
    public String setName(String name) {
    	this.name = name;
        return name;
    }
}

위와 같이 setName을 성정하자마자 바로 return 할 수 있도록 코드를 작성해 주어야한다.

 

요약

1. 싱글톤 패턴은 객체를 단 하나 생성하고 재활용할수있게하는 디자인 패턴이다.

2. 메모리를 아낄수있는 장점이있지만, 동시에 많은 단점을 보유하고있다.

3. 스프링 컨테이너를 이용하여 이러한 단점을 보완할 수 있다.

4. 싱글톤 패턴(스프링 컨테이너)은 무결성하게 짜야한다. 

'프로그래밍 > Spring' 카테고리의 다른 글

[Spring] @Transactional  (0) 2022.03.28
[Spring] SpringSecurity 비밀번호 암호화  (0) 2022.03.15
[Spring] 의존성주입(Dependency Injection, DI)  (0) 2022.03.11
[Spring] ResponseEntity  (0) 2022.03.07
[Spring] Bean  (0) 2022.03.02