본문 바로가기

Spring

[Spring] 싱글톤(Singleton)


 

준비


이전 게시물인 [Spring] 간단한 서비스 생성 및 테스트에서 작성된 코드가 있어야 본 글의 코드 예제을 수행할 수 있습니다.

 

스프링 없는 컨테이너만 사용한 객체 생성 방법


sample_Project패키지에 singletonTest.java 클래스를 생성합니다.

 

package developingman.sample_Project;

import developingman.sample_Project.member.MemberService;

public class SingletonTest {
    static DependencyConfig dependencyConfig = new DependencyConfig();

    static MemberService memberService1 = dependencyConfig.memberService();
    static MemberService memberService2 = dependencyConfig.memberService();

    public static void main(String[] args){
        System.out.println("memberService1 : " + memberService1);
        System.out.println("memberService2 : " + memberService2);

    }
}

 

아래와 같은 결과를 확인할 수 있습니다.

 

 

같은 memberService를 사용하지만 뒤에 붙은 주소값이 다른 것을 확인할 수 있습니다.

수많은 객체를 생성하게 되면 위의 방식은 메모리 낭비와 효율성이 떨어지게 됩니다.

싱글톤 패턴을 사용하여 위와 같은 문제를 해결할 수 있습니다.

 

싱글톤 패턴 적용 코드

먼저 sample_Project 패키지에 singleton 패키지를 생성합니다.

이후 싱글톤 패키지에 SingletonService.java 파일을 생성하고, 아래의 코드를 작성합니다.

 

package developingman.sample_Project.singleton;

public class SingletonService {

    // static 영역에 1개의 객체를 생성합니다.
    private static final SingletonService instance = new SingletonService();

    //객체 인스턴스가 필요하면 아래 public static 메서드를 통해서만 조회할 수 있도록 합니다.
    public static SingletonService getInstance(){
        return instance;
    }
    
    // 생성자를 private로 선언하여 외부에서 new 키워드를 통해 객체를 생성할 수 없도록 합니다.
    private SingletonService() {}
}

 

일전에 작성하였던 SingletonTest.java 클래스를 수정합니다.

 

package developingman.sample_Project;

import developingman.sample_Project.singleton.SingletonService;

public class SingletonTest {

    static SingletonService singletonService1 = SingletonService.getInstance();
    static SingletonService singletonService2 = SingletonService.getInstance();

    public static void main(String[] args){
        System.out.println("memberService1 : " + singletonService1);
        System.out.println("memberService2 : " + singletonService2);

    }
}

 

아래와 같은 출력결과를 확인합니다.

 

 

위와 같이 같은 SingletonService를 사용하는 모든 객체의 주소가 같아짐을 확인할 수 있습니다.

 

스프링을 이용한 싱글톤 방법


스프링 컨테이너의 기본값(default)은 싱글톤입니다.

 

싱글톤 패턴의 문제점

  • 싱글톤 패턴을 구현하는 코드 자체가 많습니다.
  • 의존관계상 클라이언트가 구체 클래스에 의존합니다.
  • 지정해서 가져오기 때문에 테스트하기 어렵습니다.
  • private 생성자를 사용하여 자식 클래스를 만들기 어렵기 때문에 유연성이 떨어집니다
  • 속성공유
    • 멀티쓰레드 환경에서 싱글톤 객체의 속성은 여러 쓰레드에 의해 바뀔 수 있습니다.
    • A 쓰레드에선 속성 값을 x로 바꾸고 출력하는 과정에서 B 쓰레드가 속성 값을 y로 바꾸면 쓰레드 A에선 예상하지 못한 값이 나올 수 있습니다. (1개의 인스턴스에서 속성 값을 공유하기 때문에 발생하는 문제점입니다.)
    • 가급적 읽기만 가능해야 합니다.
  • Application 초기 구동 시 인스턴스 생성
    • 싱글톤 빈은 기본적으로 애플리케이션 구동 시 생성되므로 싱글톤 빈이 많을수록 구동 시간이 증가할 수 있습니다.

 

싱글톤 패턴 문제의 해결

  • 싱글톤 패턴문제를 싱글톤 컨테이너가 해결해 줍니다.
  • 스프링 컨테이너는 싱글톤 컨테이너 역할을 합니다.
  • 싱글톤 객체로 생성하고 관리하는 기능을 싱글톤 레지스트리라고 합니다.
  • 스프링 컨테이너의 기능 덕분에 싱글턴 패턴의 모든 단점을 해결하여 객체를 싱글톤으로 유지할 수 있습니다.

 

dependencyConfig.java 클래스를 수정합니다.

 

package developingman.sample_Project;

import developingman.sample_Project.member.MemberRepository;
import developingman.sample_Project.member.MemberService;
import developingman.sample_Project.snack.SnackRepository;
import developingman.sample_Project.snack.SnackService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DependencyConfig {
    @Bean
    public MemberService memberService() {
        return new MemberService(new MemberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }
    
    @Bean
    public SnackService snackService(){
        return new SnackService(new SnackRepository());
    }
    @Bean
    public SnackRepository snackRepository() {
        return new SnackRepository();
    }
}

 

  • @Configuration과 @Bean 애너테이션을 추가합니다.
  • @Bean을 통해 스플이 컨테이너에 등록됩니다.

 

SingletonTest.java클래스를 수정하여 싱글톤 컨테이너 테스트 코드를 추가하겠습니다.

package developingman.sample_Project;

import developingman.sample_Project.member.MemberService;
import developingman.sample_Project.singleton.SingletonService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SingletonTest {

    static AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(DependencyConfig.class);

    static MemberService memberService1 = ac.getBean("memberService", MemberService.class);
    static MemberService memberService2 = ac.getBean("memberService", MemberService.class);

    public static void main(String[] args){
        System.out.println("memberService1 : " + memberService1);
        System.out.println("memberService2 : " + memberService2);

    }
}

 

코드를 수정한 후 아래와 같은 결과를 확인합니다.