본문 바로가기

이론

싱글톤(singleton) 패턴

싱글톤(Singleton) 패턴이란?

스프트웨어 디자인 패턴에서 싱글톤 패턴은 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 재사용하는 형태를 말한다.

프로그램 내에서 객체가 단 하나만 존재하는 것이 보장되야 하거나, 객체의 크기가 커서 여러 번 재사용해야하는 경우에 주로 사용된다.

 

스프링 빈(Spring bean)이란?

스프링 IoC 컨테이너에 의해서 관리되고 애플리케이션의 핵심을 이루는 객체들을 스프링 빈이라고 한다. 빈은 스프링 컨테이너에 의해서 인스턴스화되어 조립되고 관리된다. 스프링 컨테이너가 관리해준다는 점을 제외하면 자바 객체이다.

 

스코프 : 존재할 수 있는 범위

즉, 빈 스코프는 스프링 빈이 존재할 수 있는 범위를 뜻한다. 기본적으로 스프링 컨테이너에서 스프링 빈이 싱글톤 스코프로 생성되었기 때문에, 스프링 컨테이너와 생명주기를 같이 했기에 신경쓸 필요가 없었다.

하지만, 빈 스코프를 어떻게 설정하느냐에 따라 스프링 빈의 생성과 소멸을 클라이언트에서 관리해줘야하는 경우도 생길 수 있고, 다양한 요구사항에 맞는 스코프를 지정해 사용할 수 있다.

 

웹 관련 스코프

- request: 웹 요청이 들어오고 나갈때까지 유지되는 스코프

- session: 웹 세션이 생성되고 종료될 때까지 유지되는 스코프

- application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

 

 

[싱글톤 스코프의 스프링 빈 요청]

 

 

[프로토타입 스코프의 스프링 빈 요청]

여기서 프로토타입은 싱글톤 타입의 스프링 빈과는 다르게 빈 생성, 의존관계 주입, 초기화까지만 진행한다. 그렇기에 그 이후 스프링 빈을 클라이언트에 반환한 이후로는 관리하지않기에 소멸 메서드같은 것은 모두 클라이언트에서 자체적으로 관리해야 한다.

 

※싱글톤 정리

  • 하나의 인스턴스를 생성해서 공유하는 형식이므로 객체의 상태를 유지하게(stateful) 설계하면 안된다.
  • 특정 클라이언트에 의존적이거나 값을 변경할 수 있는 필드가 있으면 안된다.
  • 가급적 읽기만 가능해야 한다.
  • 필드 대신 공유되지 않는 지역변수, 파라미터, ThreadLocal을 쓰자.

※프로토타입 정리

  • 싱글톤은 스프링 컨테이너와 생명주기를 같이하지만, 프로토타입 스프링 빈은 생명주기를 달리한다.
  • 싱글톤 스프링 빈은 매번 컨테이너에서 동일한 인스턴스를 반환하지만, 프로토타입 스프링 빈은 스프링 컨테이너에 요청할 때마다 새로운 스프링 빈을 생성 후 의존관계까지 주입 및 초기화 진행 후 반환한다.
  • 프로토타입 스프링 빈은 소멸 메서드가 호출되지않는다.
  • 클라이언트가 프로토타입 스프링 빈은 직접 관리해야 한다. (소멸 메서드도 직접 호출해야 한다.)

 


프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점

→ 싱글톤 스프링 빈 내부에 의존관계로 주입되는 스프링 빈이 프로토타입인 경우

  • 프로토타입 빈은 프로토타입 스코프지만 클라이언트 빈은 싱글톤 스코프이기 때문에, 싱글톤 빈에서 프로토타입 빈을 사용한다.
  • 싱글톤 빈의 스코프는 스프링 컨테이너와 같은데, 프로토타입 스코프의 스프링 빈이 새로 생성되기는 했지만 싱글톤 빈과 함께 사용되기 때문에 계속 유지된다.
  • 그래서 빈을 2회 요청하지만 동일한 프로토타입 빈을 사용하게 되어 count를 1씩 증가했을 때 1이 아닌 2가된다.
  • 프로토타입 빈만 클라이언트가 직접 사용하는 경우라면 상관없지만 싱글톤 빈과 함께 사용하면서 프로토타입 빈이 자기의 스코프를 지키고 매번 새롭게 생성하기 위해서는 어떻게 해야할까?

문제해결 - Provider

위에서 싱글톤 빈과 프로토타입 빈을 혼용하는 경우 프로토타입의 의도대로 동작하지 않는 문제점이 발견됐다.

그럼 어떻게 싱글톤 빈과 혼용하더라도 프로토타입 빈을 매번 새롭게 생성하면서 사용할 수 있을까?

간단히 사용해보면 싱글톤 빈에서 프로토타입 빈을 매번 새로 호출해서 사용하는 방법이 있을 것이다.

static class ClientBean{
	@Autowired
    private ApplicationContext ac;

    public int logic() {
	PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
        prototypeBean.addCount();
        int count = prototypeBean.getCount();
        return count;
    }

}

- 매번 프로토타입 빈을 새로 생성하는 것을 확인할 수 잇다.

- 이렇게 의존관계를 외부에서 주입받는 것이 아닌 직접 필요한 의존관계를 찾는 것을 Dependency Lookup(DL) 의존관계 조회(탐색)이라 한다.

- 하지만, 이렇게 스프링 애플리케이션 컨텍스트 전체를 주입받게되면 스프링 컨테이너와 종속성이 생기고 테스트도 어려워진다.

 

ObjectFactory, ObjectProvider

  • ObjectFactory: 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공해준다. 아주 단순하게 getObject 하나만 제공하는 FunctionalInterface이고, 별도의 라이브러리도 필요없다. 그리고 스프링에 의존한다.
  • ObjectProvider: ObjectFactory에 편의기능들을 추가해서 만들어진 객체. 별도의 라이브러리는 필요없고 스프링에 의존한다.
static class ClientBean{
    @Autowired
    private ObjectProvider<PrototypeBean> prototypeBeanProvider;

    public int logic() {
        PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
        prototypeBean.addCount();
        int count = prototypeBean.getCount();
        return count;
    }
}

- 위에서 실행한 것과 동일하게 매번 새로운 프로토타입 빈이 생성되는 것을 확인할 수 잇다.

- ObjectProvider의 getObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.(DL)

- 스프링에 종속적인 것은 동일하지만, 기능이 단순해서 단위테스트 및 Mock을 이용한 테스트 더블을 준비하기 쉽다.

 

 

프로토타입을 쓰는 경우

싱글톤의 경우 객체를 1개만 생성한다는 장점은 있으나 Web같이 여러명이 같은 객체를 사용하는 과정에서 문제가 발생할 소지가 있다. 
예를 들어, 클래스의 멤버변수를 만들고 그것을 메소드에 넣은 파라미터가 해당 멤버변수의 값을 변경할 수 있는 구조로 설계가 되어있을 경우, 여러 사람이 같은 객체를 사용하기 때문에 내가 변경한 값을 다른 사람이 또 변경시킬수 있는 문제가 있다. 

 

'이론' 카테고리의 다른 글

2024-07-30  (0) 2024.07.30
2024-07-29  (0) 2024.07.29
DB 발전 과정  (1) 2023.11.14
Session VS JWT  (1) 2023.11.12
스프링(Spring)이란?  (0) 2023.11.09