스프링 컨텍스트와 싱글톤 레지스트리

2022. 1. 15. 16:04북리뷰/토비의 봄

728x90

전 포스팅에서, DaoFactory와 같이 직접 클래스간 의존성을 설정해서 사용하는 것과, @Configuration 애너테이션을 추가하여 스프링 애플리케이션 컨텍스트를 통해 사용하는 것의 차이는 무엇일까? 테스트 결과로는 같은데 말이다.

스프링 컨텍스트와 클래스 직접 생성의 차이

차이점을 확인하기 위해 userDao()메서드를 두 번 호출해서 리턴되는 UserDao오브젝트를 비교하자

DaoFactory factory = new DaoFactory();
UserDao dao1 = factory.userDao();
UserDao dao2 = factory.userDao();

System.out.println(dao1);
System.out.println(dao2);

출력 결과에서 알 수 있듯이, 두 개는 각기 다른 값을 가진 동일하지 않은 오브젝트다.
즉, 따로따로 생성되었다는 것을 알 수 있다.

이번에는 스프링의 애플리케이션 컨텍스트에서 Config를 설정정보로 등록하고 getBean()메서드로 userDao를 생성해보자

 ApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);

        UserDao dao3 = context.getBean("userDao", UserDao.class);
        UserDao dao4 = context.getBean("userDao", UserDao.class);

        System.out.println(dao3);
        System.out.println(dao4);

두 오브젝트의 출력 값이 같으므로, getBean()을 두 번 호출해서 가져온 오브젝트가 동일하다는 사실을 알 수 있다.
스프링은 여러 번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다는 것이다.

애플리케이션 컨텍스트는 우리가 직접 만든 DaoFactory와 비슷한 방식으로 동작하는 IoC컨테이너다. 그러면서 동시에 이 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리(singleton registry) 이기도 하다.
스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다.
여기서 싱글톤이라는 것은 디자인 패턴에서 나오는 싱글톤 패턴과 비슷한 개념이지만 그 구현 방법은 확연히 다르다.

서버 애플리케이션과 싱글톤

왜 스프링은 싱글톤으로 빈을 만들까? 이는 스프링이 주로 적용되는 대상이 자바 엔터프라이즈 기술을 사용하는 서버환경이기 때문이다.
스프링이 처음 설계됐던 서버환경은 서버당 최대로 초당 수십에서 수백 번씩 브라우저나 여타 시스템으로부터의 요청을 받아 처리할 수 있는 높은 성능이 요구되는 환경이었다. 그런데 매번 클라이언트에서 요청이 올 때마다 각 로직을 담당하는 오브젝트를 새로 만들어서 사용한다면 요청 한 번에 5개의 오브젝트가 생성하고, 초당 500개의 요청이 들어오면 초당 2500개의 새로운 오브젝트가 생성된다. 아무리 GC의 성능이 좋아졌다 한들 이렇게 부하가 걸리면 서버가 감당하기 힘들다.
그래서 엔터프라이즈 분야에서는 서비스 오브젝트라는 개념을 일찍부터 사용해왔다. 서블릿은 자바 엔터프라이즈 기술의 가장 기본이 되는 서비스 오브젝트라 할 수 있다. 스펙에서 강제하진 않지만, 서블릿을 대부분 멀티스레드 환경에서 싱글톤으로 동작한다. 서블릿 클래스당 하나의 오브젝트만 만들어두고, 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다.

싱글톤 패턴의 한계

자바에서 싱글톤을 구현하는 방법은 보통 이렇다.

  1. 클래스 밖에서는 오브젝트를 생성하지 못하도록 생성자를 private로 만든다.
  2. 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의한다.
  3. 스태틱 팩토리 메서드인 getInstance()를 만들고 이 메서드가 최초로 호출되는 시점에서 한 번만 오브젝트가 만들어지게 한다. 생성된 오브젝트는 스태틱 필드에 저장된다. 또는 스태틱 필드의 초기값으로 오브젝트를 미리 만들어둘 수도 있다.
  4. 한번 오브젝트(싱글톤)가 만들어지고 난 후에는 getInstance() 메서드를 통해 이미 만들어져 스태틱 필드에 저장해둔 오브젝트를 넘겨준다.일반적으로 싱글톤 패턴 구현 방식에는 다음과 같은 문제가 있다.싱글톤 패턴은 생성자를 private로 제한한다. 문제는 private 생성자를 가진 클래스는 다른 생성자가 없다면 상속이 불가능하다. 객체지향의 장점인 상속과 이를 이용한 다형성을 적용할 수 없다.싱글톤은 테스트하기가 어렵거나 테스트 방법에 따라 아예 테스트가 불가능하다. 싱글톤은 만들어지는 방식이 제한적이기 떄문에 테스트에서 사용될 때 목 오브젝트 등으로 대체하기가 힘들다.서버에서 클래스 로더를 어떻게 구성하고 있느냐에 따라서 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있다. 이렇게 되면 싱글톤으로서의 가치가 떨어진다.싱클톤의 스태틱 메서드를 이용해 언제든지 싱글톤에 쉽게 접근할 수 있기 때문에 애플리케이션 어디서든지 사용될 수 있고, 그러다 보면 자연스럽게 전역 상태(global state)로 사용되기 쉽다. 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는 프로그래밍 모델이다.
  5. 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.
  6. 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
  7. 싱글톤은 테스트하기가 힘들다.
  8. private 생성자를 갖고 있지 때문에 상속할 수 없다.
public class UserDao {
    private static UserDao UNSTANCE;
    ...
    private UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }
    public static synchronized UserDao getInstance() {
        if(INSTANCE == null) INSTANCE = new UserDao(???); return INSTANCE;
    }
    ...
}

싱글톤 레지스트리

스프링은 서버환경에서 싱글톤 패턴을 적극 지지한다. 그러나, 싱글톤 패턴의 구현 방식은 단점이 있기 때문에, 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다.그것이 바로 싱글톤 레지스트리(singleton registry)다. 스프링 컨테이너는 싱글톤을 생성하고, 관리하고, 공급하는 싱글톤 관리 컨테이너이기도 하다. 싱글톤 레지스트리의 장점은 스태틱 메서드와 private 생성자를 사용해야하는 비정상적인 클래스가 아니라 평범한 자바 클래스를 싱글톤으로 활용하게 해준다는 점이다. 평범한 자바 클래스라도 IoC 방식의 컨테이너를 사용해서 생성과 관계설정, 사용 등에 대한제어권을 컨테이너에게 넘기면 손쉽게 싱글톤 방식으로 만들어져 관리되게 할 수 있다.

728x90