토비의 스프링 정리 프로젝트 #1.6 싱글톤 레지스트리와 오브젝트 스코프 (싱글톤 패턴의 한계, 스프링을 이용한 싱글톤 단점 극복)

Jake Seo·2021년 7월 7일
0

토비의 스프링

목록 보기
7/29

빈의 동일성

동등성(equivalent)동일성(identical)

  • 동등성.equals() 메소드의 결과가 true인 경우 동등하다고 말한다.
  • 동일성은 동일한 객체(같은 주소를 가진 레퍼런스)인 경우 동일하다고 말한다.

동일한 경우 동등하지만, 동등한 경우는 동일하지 않을 수도 있다.

동일성의 관점에서 봤을 때, DaoFactory에서 생성한 UserDao는 매번 new 생성자를 통해 생성하기 때문에 동일하지 않지만, ApplicationContext에서 .getBean()으로 가져온 UserDao는 동일하다.

여기서 알 수 있는 사실은 스프링은 빈을 요청했을 때 매번 동일한 오브젝트를 돌려준다는 것이다.

물론 특정한 빈에 대해서는 설정을 바꿔서 매번 새로 빈을 생성하게 만들 수도 있지만, 기본 동작은 동일한 빈을 반환한다.

싱글톤 레지스트리로서의 애플리케이션 컨텍스트

애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리(Singleton Registry)의 관점에서 볼 수도 있다. 별다른 설정을 하지 않으면 모든 빈을 싱글톤으로 만든다.

스프링 싱글톤 레지스트리에서 다루는 싱글톤은 일반적인 싱글톤 디자인 패턴과 다른 방법으로 구현된다. 일반 싱글톤 디자인 패턴에서의 많은 단점을 해소한 버전이다. 이는 뒤에서 설명한다.

왜 싱글톤 방식을 쓸까?

그 이유는 스프링 프레임워크가 동작하는 환경이 대부분 서버환경인데 있다. 서버는 내부적으로 계층별로 나뉜 수많은 오브젝트(데이터 액세스 로직, 서비스 로직, 비즈니스 로직, 프레젠테이션 로직 등)를 이용해서 사용자의 요청을 처리해준다.

요청 1개에 5개의 오브젝트가 관여한다고 했을 때 싱글톤을 사용하지 않는다면, 1000명의 사용자가 해당 기능을 동시에 요청한다면 5000개의 오브젝트를 만들었다가 다시 제거해야 한다. 이 과정에서 오브젝트가 만들어지고 사라지면서 생기는 오버헤드는 어마어마하다.

그래서 엔터프라이즈 분야에서는 서비스 오브젝트라는 개념을 일찍이부터 사용했는데, 대표적으로는 서블릿이 있다. 스펙에서 강제하진 않지만, 서블릿은 대부분 멀티스레드 환경에서 싱글톤으로 동작한다. 서블릿 클래스당 하나의 오브젝트만 만들어두고, 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다.

오브젝트 생성과 제거에 대한 오버헤드를 해결하기 위해 애플리케이션에 제한된 수 혹은 한 개의 오브젝트만 만들어서 사용하는 것이 싱글톤 패턴의 원리이다. 서버 환경에서는 서비스 싱글톤의 사용이 권장된다.

싱글톤 패턴의 한계

싱글톤 패턴은 사실 사용하기 까다롭고 여러가지 대표적인 문제점들을 보유하고 있어서 안티 패턴이라고도 불린다.

자바에서 싱글톤 패턴을 구현하는 방법은 보통 다음과 같다.

  • 생성자를 private으로 만들어서 다른 곳에서 생성되지 못하게 만든다.
  • 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의한다.
  • 스태틱 팩토리 메소드인 .getInstance()를 만들고, 이 메소드가 최초로 호출되는 시점에 한 번만 오브젝트가 만들어지게 한다. 생성된 오브젝트는 스태틱 필드에 저장된다.
    • 혹은 초기값으로 미리 인스턴스를 생성하여 스태틱 필드에 넣어놓는다.
  • 인스턴스 생성 후에는 .getInstance()를 통해서만 스태틱 필드에 저장해둔 오브젝트에 접근 가능하다.
public class UserDaoSingleton {
    private static UserDaoSingleton INSTANCE;
    
    private UserDaoSingleton() {
    }
    
    public static synchronized UserDaoSingleton getInstance() {
        if(INSTANCE == null) INSTANCE = new UserDaoSingleton();
        return INSTANCE;
    }
}

위는 싱글톤 구현의 예이다. 싱글톤으로 오브젝트를 만들게 되면 UserDao를 생성하며 ConnectionMaker 의 구현체를 주입해주는 것이 불가능해진다. UserDao에 갑자기 싱글톤 패턴을 도입하는 것은 무리가 있어 보인다.

싱글톤 패턴의 일반적 문제에 대해 알아보자.

private 생성자로 인해 상속이 불가능하다.

private 생성자를 지닌 클래스는 다른 생성자가 없다면 상속이 불가능하다. 객체지향의 장점인 상속과 이를 이용한 다형성을 적용할 수 없다. 객체지향의 특징이 적용되지 않는 스태틱 필드와 메소드를 사용하는 것도 문제다.

싱글톤은 테스트하기 힘들다.

싱글톤은 만들어지는 방식이 제한적이어서 목 오브젝트 등으로 대체하기 힘들다. 싱글톤은 초기화 과정에서 필요한 오브젝트 등을 다이나믹하게 주입해줄 수 없다. 이 경우 테스트용 오브젝트로 대체하기 힘들다. 테스트는 엔터프라이즈 개발의 핵심인데 테스트를 만드는데 지장이 있다는 건 큰 단점이다.

서버 환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.

서버에서 클래스 로더를 어떻게 구성하냐에 따라 싱글톤임에도 하나 이상의 오브젝트가 만들어질 수 있다. 따라서 자바 언어를 이용한 싱글톤 패턴 기법은 서버 환경에서는 싱글톤이 보장된다고 볼 수는 없다.

여러 개의 JVM에 분산돼서 설치되는 경우에도 각각 독립적으로 오브젝트가 생성되기 때문에 싱글톤으로서의 가치가 떨어진다.

전역 상태를 만들 수 있기 때문에 바람직하지 못하다.

아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는 프로그래밍 모델이다. 스태틱 필드와 메소드만으로 구성된 클래스를 사용하는 것과 다를 게 무엇인가?

싱글톤 레지스트리

이전까지의 내용을 정리해보면 서버 환경에서 싱글톤 서비스 오브젝트를 사용하는 것은 지지했으나, 자바의 기본 싱글톤 패턴 구현 방식은 여러가지 단점이 있었다.

스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공하는데 그것이 바로 싱글톤 레지스트리(Singleton Registry)이다. 스프링 컨테이너는 싱글톤을 생성하고, 관리하고 공급하는 싱글톤 관리 컨테이너이기도 하다.

스프링이 관리하는 싱글톤 레지스트리를 사용하면 해결되는 문제에 대해 알아보자.

평범한 자바 클래스를 싱글톤으로 활용하게 해준다.

평범한 자바 클래스라도 IoC 방식의 컨테이너를 사용해서 생성, 관계설정, 사용 등에 대한 제어권을 컨테이너에게 넘기면 손쉽게 싱글톤 방식으로 만들어져 관리되게 할 수 있다.

스프링을 이용하면 더 이상 싱글톤을 만들기 위해 스태틱 메소드, private 생성자 등을 이용할 필요가 없다.

오브젝트 생성 권한이 모두 애플리케이션 컨텍스트에 있기 때문에 가능하다.

테스트에서 싱글톤 방식으로 사용될 클래스를 활용할 수 있다.

위에서 설명했듯 더이상 스태틱 메소드, private 생성자 등의 제약 조건이 없어서 일반적인 클래스처럼 싱글톤으로 사용될 클래스도 테스트에 사용하는데 아무런 제약이 없다.

테스트 환경에 따라 자유롭게 오브젝트를 만들거나, 목 오브젝트로 대체하는 것도 간단하다. 또한 당연히 이제는 생성자 파라미터를 통해 구현체를 주입하는 것에도 아무런 문제가 없다.

싱글톤 레지스트리의 장점 정리

스프링은 싱글톤 패턴을 사용하면서도 객체지향적인 설계방식과 원칙, 디자인 패턴 등을 적용하는데 아무런 제약이 없게 만들어주었다. 스프링은 IoC 컨테이너일 뿐만 아니라, 고전적인 싱글톤 패턴을 대신해서 싱글톤을 만들고 관리해주는 싱글톤 레지스트리이다.

싱글톤 주의점

싱글톤은 stateless여야 한다.

싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는 상태 정보를 내부에 갖고 있지 않은 무상태(stateless) 방식으로 만들어져야 한다.

물론 단순 읽기 전용 값을 상태로 가지는 것은 아무런 문제가 없겠지만, 공유되는 상태 정보를 가지면 다른 사용자들에 의해 내가 의도하지 않은 방향으로 해당 값이 바뀌고 읽어지고 할 확률이 매우 크다.

단, 파라미터, 로컬 변수, 리턴 값 등의 이용은 자유롭다. 요청 정보, DB 서버 리소스로부터 얻은 정보 등은 자유롭게 사용할 수 있는 파라미터, 로컬 변수, 리턴 값 등을 이용해 처리하면 된다.

또한 자신이 사용하는 다른 싱글톤 빈을 저장하려는 용도라면 인스턴스 변수를 사용해도 무관하다. 이를테면 UserDao 빈에서는 ConnectionMaker 빈의 주입 정보를 기억하기 위한 필드를 만들어도 무방하다. 이는 읽기 전용 값을 가지는 것과 비슷하게도 볼 수 있다.

일반적으로 생성자 주입에서 접근 지시자 private final을 이용하여 빈의 주입 정보를 기억한다.

스프링 빈의 스코프

스프링이 관리하는 오브젝트인 빈이 생성되고 존재하고 적용되는 범위를 빈의 스코프라고 한다. 스프링 빈의 기본 스코프는 싱글톤이며, 이는 스프링 컨테이너가 존재하는 동안에는 계속 유지된다.

그러나 경우에 따라서는 싱글톤 외에 다른 스코프를 가질 수도 있다. 대표적으로 프로토타입 스코프가 있는데, 싱글톤과 달리 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트를 만들어준다. 이 외에도 HTTP 요청이 생길 때마다 생성되는 요청 스코프도 있고, 웹의 세션과 유사한 스코프를 가지는 세션 스코프도 있다.

이는 나중에 10장에서 알아본다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글