본문 바로가기

Spring/Spring Framework

SpringFramework에서의 싱글톤 전략

반응형

애플리케이션 동작 영역 (JVM ) 에서 단 하나의 인스턴스만 만들어야할 경우,

또는 리소스 절약을 위해 하나의 인스턴스만 만들고 이를 공유하도록 설계하고자 할 경우, 싱글톤 패턴을 적용한다.

 

자바 언어를 이용해서 싱글톤 패턴을 적용할 수 있다.

인스턴스는 public static final 맴버로, 생성자는 private으로 설정하고

해당 맴버를 반환하는 getInstance 메서드를 생성하여, 이 메소드를 통해서만 인스턴스를 가져올 수 있도록 설정해주면 된다.

그러나 여기에는 몇가지 문제점이 있다.

 

  • 싱글톤 패턴을 구현하는 코드 자체가 복잡해진다.
  • 의존 관계상 클라이언트가 구현체 클래스에 의존한다 -> DIP 위반
  • 구현체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
  • 테스트가 어렵다. (mock 구현으로 대체할 수 없음)
    singleton 사용으로 코드 간의 강결합으로 변경에 따른 영향도는 굉장히 커졌지만 그 변경에 대해 검증하기엔 매우 힘들다.
  • 내부 속성을 변경하거나 초기화하기 어렵다. => 상태가 없는 객체여야한다.
  • private 생성자로 자식 클래스를 만들기가 어렵다.
    상속과 다형성 적용 불가로 객체 지향적 설계 실패

이러한 이유로 유연성이 떨어지며, 안티패턴으로 불리기도 한다.

 

스프링은 이러한 문제점을 해결해주는 싱글톤 컨테이너 를 제공한다.

싱글톤 컨테이너란, 스프링 컨테이너 내에서 공유 빈(singleton)으로 설정할 경우(Spring Bean Scope의 기본 설정이 sigleton임), 이를 따로 관리해주고, 애플리케이션 내에서 싱글톤 객체 임을 보장해준다.

이러한 기능으로, DIP, OCP, 테스트 등 자유롭게 싱글톤을 사용할 수 있다.

 그러나, 싱글톤 패턴을 적용할 경우, 다음 주의점은 명심해야한다.

 

싱글톤 방식의 주의점

싱글톤 방식은 하나의 동일한 객체 인스턴스를 공유하기 때문에, 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다.
싱글톤 패턴은 무상태(stateless)로 설계해야 한다
따라서, 필드 대신 Java에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용하여 해결할 수 있다.

스프링 빈의 필드에 공유 값을 설정하면 큰 장애가 발생할 수 있다

그렇다면 스프링 싱글톤 컨테이너에 의한 관리를 받기 위해서는 어떻게 설정해야할까?

 

@Configuration 애노테이션을 단 config 파일에 빈을 정의해주면 된다.

@Slf4j
@Configuration
public class AppConfig {

    @Bean
    public UserRepositoryDao userRepository(){
        log.info("call userRepository");
        return new UserRepositoryDao();
    }

    @Bean
    public UserService userService(){
        log.info("call userService");
        return  new UserServiceImpl(userRepository());
    }
}

@SpringBootTest
class DemoSpringMvcApplicationTests {

    @Test
    void singletonTest() {

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        UserServiceImpl userService = applicationContext.getBean("userService", UserServiceImpl.class);
        System.out.println(userService);
        System.out.println(userService.getUserRepository());

        UserRepository userRepository =  applicationContext.getBean("userRepository", UserRepository.class);
        System.out.println(userRepository);

        BeanDefinition userServiceBeanDefinition =new RootBeanDefinition( userService.getClass());
        System.out.println("UserService Bean Definition Scope =" + userServiceBeanDefinition.getScope());
        BeanDefinition userRepositoryBeanDefinition =new RootBeanDefinition( userRepository.getClass());
        System.out.println("UserRepository Bean Definition Scope = " + userRepositoryBeanDefinition.getScope());

        assert userRepository == userService.getUserRepository();

    }
}

실제 테스트해 본 결과

userService 의 userRepository 와 userRepository 의 빈이 같은 인스턴스임을 알 수 있다.

또한, 빈의 스코프를 조회해본 결과 singleton 으로 생성되었음을 알 수 있다.

(스코프 값이 없는 것 처럼 보이지만 "" 이 곧 singleton 임을 아래 코드를 보면 알 수 있다.)

 

실제 application context 에서 빈을 가져오는 getBean 메소드의 코드를 보면,

싱글톤 컨테이너에 이미 인스턴스가 존재할 경우, getObjectBeanInstance 메소드를 이용해서 캐싱되어있는 공유 빈을 가져온다.

 

<참고>스프링 빈 Scope

  • singleton : 스프링 default bean scope
  • prototype : 애플리케이션 요청시 ( getBean() 메서드가 호출될 때마다) 스프링이 새 인스턴스를 생성합니다.
  • request : HTTP 요청별로 인스턴스화 되며 요청이 끝나면 소멸됩니다.
  • session : HTTP 세션별로 인스턴스화 되며 세션이 끝나면 소멸됩니다.
  • global session : 포틀릿 기반의 웹 애플리케이션 용도로 전역 세션 스코프가 빈과 같은 스프링 MVC를 사용한 포탈 애플리케이션 내의 모든 포틀릿 사이에 공유를 할 수 있습니다.
  • thread : 새 스레드에서 요청하면 새로운 bean 인스턴스를 생성합니다. 같은 스레드의 요청에는 항상 같은 인스턴스가 반환됩니다.
  • custom : org.pringframework.beans.factory.config.Scope를 구현하고 커스텀 스코프를 스프링의 설정에 등록하여 사용합니다.

※ request, session, global session의 스코프는 일반 spring 애플리케이션이 아닌 Spring MVC Application에서만 제공

반응형

'Spring > Spring Framework' 카테고리의 다른 글

재사용성과 다이나믹 디스패치, 더블 디스패치  (0) 2021.01.02
Restful API  (0) 2020.11.06
다국어 처리하기  (0) 2020.03.07
Rest API  (0) 2019.12.16
Spring Task  (0) 2019.10.04