본문 바로가기

Spring/토비의 스프링 3.1

1.6 싱글톤 레지스트리와 오브젝트 스코프


1.6 싱글톤 레지스트리와 오브젝트 스코프

스프링 애플리케이션 컨텍스트와 기존 오브젝트 팩토리의 차이점

오브젝트의 동일성과 동등성
동일성(identity) : 완전히 같은 오브젝트, == 연산자 이용
동등성(equality) : 동일한 정보를 갖고 있는 오브젝트, equals() 연산자 이용

DaoFactory의 userDao()를 여러 번 호출했을 때 동일한 오브젝트가 돌아오는가?
userDao()를 호출할 때마다 new 연산자에 의해 새로운 오브젝트가 생성되므로 다른 오브젝트가 리턴될 것이라 예상

DaoFactoryIdentityTest.class

public class DaoFactoryIdentityTest {
    public static void main(String[] args) {
        DaoFactory factory = new DaoFactory();

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

        System.out.println("ObjectFactory");
        System.out.println("ObjectFactory dao1: " + dao1);
        System.out.println("ObjectFactory dao2: " + dao2);
        System.out.println("dao1 == dao2 : " + (dao1==dao2));
    }
}
ObjectFactory
ObjectFactory dao1: com.david.tobysspring.user.dao.UserDao@15db9742
ObjectFactory dao2: com.david.tobysspring.user.dao.UserDao@6d06d69c
dao1 == dao2 : false

두 개는 서로 다른 값을 가진 동일하지 않은 오브젝트이다.
그렇다면, 스프링 애플리케이션 컨텍스트를 이용하면 어떨까?

public class DaoFactoryIdentityTest {
    public static void main(String[] args) {
        // ...
        ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);

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

        System.out.println("ApplicationContext");
        System.out.println("ApplicationContext dao3: " + dao3);
        System.out.println("ApplicationContext dao4: " + dao4);
        System.out.println("dao3 == dao4 : " + (dao3==dao4));
    }
}
ApplicationContext
ApplicationContext dao3: com.david.tobysspring.user.dao.UserDao@4ba2ca36
ApplicationContext dao4: com.david.tobysspring.user.dao.UserDao@4ba2ca36
dao3 == dao4 : true

getBean()을 두 번 호출하였지만 동일한 오브젝트를 돌려준다.
즉, 스프링은 여러 번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다.

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

스프링은 기본적으로 빈 오브젝트를 모두 싱글톤으로 만든다. (디자인 패턴에서의 싱글톤 패턴과는 다르다.)

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

싱글톤으로 만드는 이유

  • 스프링 개발 환경 : 주로 엔터프라이즈급 서버 환경
  • 서버 하나당 초당 수십에서 수백번의 요청을 받아 처리할 수 있어야 하며, 비즈니스 로직 자체도 복잡한 경우 많다.
  • 매 요청마다 새로운 오브젝트를 생성하면 서버 부하를 감당할 수 없음
  • 서블릿은 대부분 멀티스레드 환경에서 싱글톤으로 동작
  • 따라서 서블릿 클래스당 하나의 오브젝트만 만들어두고, 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용

싱글톤 패턴(Singletone Patter) - 디자인 패턴으로서
디자인 패턴에서 가장 자주 활용되지만 가장 많은 비판을 받기도 함.
어떤 클래스를 애플리케이션 내에서 제한된 인스턴스 개수(주로 하나)만 존재하도록 강제
애플리케이션 내에서 전역적으로 접근이 가능함
단일 오브젝트만 존재하여야 하고, 이를 애플리케이션 여러 곳에서 공유하는 경우에 주로 사용

싱글톤 패턴의 한계

자바에서 싱글톤을 구현하는 방법

  • 클래스 밖에서 오브젝트를 생성하지 못하도록 생성자를 private으로 만든다.
  • 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의한다.
  • 스태틱 팩토리 메소드인 getInstance()를 만들고 이 메소드가 최초로 호출되는 시점에 한 번만 오브젝트가 만들어지게 한다. 생성된 오브젝트는 스태틱 필드에 저장되거나 스태틱 필드의 초기값으로 오브젝트를 미리 만들어줄 수 있다.
  • 싱글톤 오브젝트가 만들어진 후에는 getInstance() 메소드를 통해 만들어진 저장해둔 오브젝트를 넘겨준다.

싱글톤 패턴을 적용한 UserDao

public class UserDao {
    private static UserDao INSTANCE;
    // ...

    private UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }

    public static synchronized UserDao getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new UserDao(???); // 오류
        }
        return INSTANCE;
    }
}

싱글톤 패턴 구현 방식의 문제점

  1. private 생성자를 갖고 있기 때문에 상속할 수 없다.
    • 객제지향의 장점인 상속과 다형성을 적용할 수 없다.
    • 스태틱 필드와 메소드 역시 마찬가지
  2. 테스트하기가 힘들다.
    • 싱글톤은 테스트하기 어렵거나 아예 테스트가 불가능함
    • 싱글톤의 생성방식이 제한적이므로 목 오브젝트 등으로 대체하기 어려움
  3. 서버 환경에서 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
    • 서버의 경우 여러 개의 JVM에 분산되어 설치되는 경우도 있음
  4. 전역 상태
    • 스태틱 메소드를 이용하여 언제든지 쉽게 접근이 가능함

싱글톤 레지스트리

싱글톤 레지스트리

  • 싱글톤 방식을 지향하지만 여러가지 단점이 존재
  • 스프링이 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공하는 것
  • 스태틱 메소드와 private 생성자가 아닌 평범한 자바 클래스를 싱글톤으로 활용 가능
  • 객체 지향 설계 방식과 원칙, 디자인 패턴(싱글톤 패턴 제외) 적용에 제약이 없음

1.6.2 싱글톤과 오브젝트의 상태

싱글톤 사용의 주의점

  1. 멀티스레드 환경에서 여러 스레드가 동시에 접근할 수 있으므로 상태 관리에 주의
  2. 무상태(상태 정보를 내부에 갖고 있지 않은 상태; stateless) 방식으로 만들어져야 함
  3. 무상태 방식의 클래스
    • 파라미터와 로컬 변수, 리턴 값 등을 이용
    • 메소드 파라미터와 로컬 변수, 리턴 값은 매번 새로운 값을 저장할 독립적인 공간이 생성되므로 여러 스레드의 값이 덮어써 질 우려가 없음

인스턴스 변수를 사용하도록 수정한 UserDao.class


public class UserDao {
    // 초기에 설정하면 사용 중에는 바뀌지 않는 읽기 전용 인스턴스 변수
    private ConnectionMaker connectionMaker;

    // 매번 새로운 값으로 바뀌는 정보를 담은 인스턴스 변수, 심각한 문제가 발생
    private Connection c;
    private User user;

    private UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }

    public User get(String id) throws ClassNotFoundException, SQLException {
        this.c = connectionMaker.makeConnection();
        // ...
        this.user = new User();
        this.user.setId(rs.getString("ID"));
        this.user.setName(rs.getString("NAME"));
        this.user.setPassword(rs.getString("PASSWORD"));
        // ...
        return user;
    }
}

기존 UserDao와 달리 Connection과 User를 클래스 인스턴스 변수로 선언

  • 멀티스레드 환경에서 싱글톤으로 사용하면 심각한 문제 발생
  • Connection, User와 같이 개별적으로 바뀌는 정보는 로컬 변수, 파라미터를 이용하여야 한다.

ConnectionMaker는 읽기 전용 정보이므로 무관하다.
코드는 다시 되돌려놓자.

public class UserDao {
    // 초기에 설정하면 사용 중에는 바뀌지 않는 읽기 전용 인스턴스 변수
    private ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }

    public void add(User user) throws ClassNotFoundException, SQLException {
        Connection c = connectionMaker.makeConnection();
        // ...
    }

    public User get(String id) throws ClassNotFoundException, SQLException {
        Connection c = connectionMaker.makeConnection();
        // ...
        User user = new User();
        user.setId(rs.getString("ID"));
        user.setName(rs.getString("NAME"));
        user.setPassword(rs.getString("PASSWORD"));
        // ...
        return user;
    }
}

1.6.3 스프링 빈의 스코프

빈의 스코프 : 빈이 생성되고, 존재하고, 적용되는 범위

  • 기본 스코프 : 싱글톤
  • 경우에 따라 다른 스코프를 가질 수 있음
  • ex) 프롵토타입(prototype) 스코프, 요청(request) 스코프, 세션(session) 스코프

' Spring > 토비의 스프링 3.1' 카테고리의 다른 글

1.8 XML을 이용한 설정  (0) 2019.01.11
1.7 의존관계 주입(DI)  (0) 2019.01.10
1.5 스프링의 IoC  (0) 2019.01.07
1.4 제어의 역전(IoC)  (0) 2019.01.07
1.3 DAO의 확장  (0) 2019.01.07