Spring - Spring의 ApplicationContext는 Singleton Registry이다.
목록  
제 목 Spring의 ApplicationContext는 Singleton Registry이다.
작성자 박세청 작성일 2013/11/08 02:21


2011/06/16 - [Framework/Spring] - AnnotationConfigApplicationContext를 이용해 Bean 가져오기.
에서 @Configure를 붙이기 전인 일반적인 오브젝트 팩토리 클래스의 userDAO() 메소드를 두번 호출하여 UserDAO 객체 두개를 받았다고 생각해보자, 코드로 구현하면 아래와 같다.

public class DaoFactory {

   public UserDAO userDAO() {

       return new UserDAO(connectionMaker());

   }

   public ConnectionMaker connectionMaker() {

       return new DConnectionMaker(); // DB 커넥션 오브젝트를 리턴해준다.

   }

}



DaoFactory factory = new DaoFactory();
UserDAO userDAO1 = factory.userDAO();
UserDAO userDAO2 = factory.userDAO(); // 2번 호출

userDAO1과 userDAO2는 같은 객체일까? 여기서 userDAO1과 userDAO2는 레퍼런스이기 때문에 메모리상에 같은 공간을 가리키고 있어야 같은 객체이다. 즉, 메모리 상에 하나의 객체만 존재하고 있어야 하는 것이다.
여기서는 userDAO1과 userDAO2는 같은 객체가 아니게 된다. 매번 new 연산자를 통해 메모리상에 새로운 공간을 만들어 내고 있는 것이다. 확인해보는 방법은 System.out.println() 메소드를 통해 userDAO1과 userDAO2의 값을 출력해보면 되는 것이다.

DaoFactory factory = new DaoFactory();
UserDAO userDAO1 = factory.userDAO();
UserDAO userDAO2 = factory.userDAO(); // 2번 호출

System.out,println(userDAO1);
System.out.println(userDAO2);

=================================================
UserDAO@118f323
UserDAO@123f353

두개의 값이 다르다. 
(userDAO1 == userDAO2) 도 false이다.
저 값은 정확히 메모리 상의 주소값은 아니지만 일단 메모리 주소값이라고 생각해도 무방할 듯 하다.

그렇다면 Spring의 ApplicationContext를 이용해서 UserDAO 객체를 가져와서 값을 출력해보자.
2011/06/16 - [Framework/Spring] - AnnotationConfigApplicationContext를 이용해 Bean 가져오기. 에서와 같이 DaoFactory를 ApplicationContext에서 Bean들의 설정정보로 참고할 수 있게 @Configurem @Bean Annotation을 붙혀서 AnnotationConfigureApplicationContext객체를 생성하자.

package applicationcontext.factory;


import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;


import applicationcontext.connectionmaker.ConnectionMaker;

import applicationcontext.connectionmaker.DConnectionMaker;

import applicationcontext.dao.UserDAO;


@Configuration

public class DaoFactory {

   @Bean

    public UserDAO userDAO() {

        return new UserDAO(connectionMaker()); // Bean 생성

    }

   @Bean

    public ConnectionMaker connectionMaker() {

        return new DConnectionMaker(); // Bean 생성

    }

}



ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDAO userDAO1 = context.getBean("userDAO", UserDAO.class);
UserDAO userDAO2 = context.getBean("userDAO", UserDAO.class); // Bean을 두번 가져온다.

System.out.println(userDAO1);
System.out.println(userDAO2);
================================================================
UserDAO@ee22f7
UserDAO@ee22f7

생성된 userDAO1과 userDAO2가 가리키는 값이 같다! 두 객체는 같은 객체이다. 즉, 메모리상에 객체는 하나만 만들어 지고 userDAO1, userDAO2는 같은 곳을 가리키고 있는 것이다.
이제 userDAO1 == userDAO2를 해보면 true인것을 확인할 수 있을 것이다.

일반적인 new연산자를 통해 객체를 생성해서 사용하는 것과 Spring의 ApplicationContext를 이용해 객체를 가져와서 사용하는 것의 차이점이 보이는가?
Spring의 ApplicationContext는 Singleton을 저장하고 관리하는 Singleton Registry 이기도 하기 때문이다.

Spring은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 모든 Bean 오브젝트를 모두 Singleton으로 생성한다.
(물론 설정을 변경해서 매번 새로운 오브젝트를 생성하게 할 수도 있다.)

왜 Spring은 Singleton으로 Bean을 생성하는 것일까?

하나의 요청(Request)을 처리하기 위해 데이터 액세스 로직(DAO), 서비스 로직, 비지니스로직, 프레젠테이션 로직 등의 다양한 기능을 담당하는 오브젝트들이 계층형 구조로 보통 이루어 지게된다. 
매번 클라이언트에서 요청(Request)이 올 때마다 각 로직을 담당하는 오브젝트들을 새로 만들어서 사용한다라고 생각해보자.
요청 한번에 5개의 오브젝트가 생성이 된다고 하고 초당 500개의 요청이 들어온다라고 하면 2500개의 새로운 오브젝트들이 생성이 된다. 아무리 Java의 오브젝트 생성과 GC(Gabage Collector)의 성능이 좋아졌다고 한들 이렇게 부하가 걸리면 서버가 감당하기 힘들다.
이때문에 엔터프라이즈 분야에서는 서비스 오브젝트라는 개념을 사용해 왔는데 Servlet은 Java 엔터프라이즈 기술의 가장 기본이 되는 서비스 오브젝트라고 할 수 있다. (Servlet은 Java 웹개발에서 사용하는데 요즘 엔터프라이즈 분야가 보통 Java를 이용한 Web App로 개발이 된다.)
Servlet은 대부분 멀티스레드 환경에서 Singleton으로 동작한다. Servlet 클래스 당 하나의 오브젝트만 만들어 주고, 사용자의 요청(Request)을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해서 사용하게 되어있다.

서버환경에서는 Singleton 사용이 권장된다. 

먼저 Singleton Pattern을 구현하는 방법을 알아보자.
- 클래스 밖에서는 오브젝트를 생성하지 못하도록 생성자를 private로 만든다.
- 생성된 Singleton오브젝트를 저장할 수 있는 자신과 같은 타입의 static 필드를 정의한다.
   private static UserDAO obj;
- static 팩토리 메소드인 getInstance()를 만들고 이 메소드가 최초로 호출되는 시점에서 한번만 오브젝트가 만들어지게 한다.
   if(obj == null) { this.obj = new UserDAO(); return this.obj; }
- 한번 singleton이 만들어지고 난 후에는 getInstance()메소드를 통해 이미 만들어져 static 필드에 저장해둔 오브젝트를 넘겨준다.

public class UserDAO {
    private static UserDAO obj; // 자신과 같은 static 필드 정의 

    // 밖에서 오브젝트를 생성하지 못하도록 생성자를 private로 구현
    private UserDAO(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }

    public static synchronized UserDAO getInstance() {
        if(this.obj == null) { // 최초 호출되는 시점.
           ConnectionMaker connectionMaker = new ConnectionMaker();
           this.obj = new UserDAO(connectionMaker );
        }
        return this.obj;
    }
}

UserDAO를 이렇게 구현을 해 놓으니 원래는 DaoFactory에서 ConnectionMaker객체를 만들어서 UserDAO객체에 넣어주었는데 생성자가 private라 DaoFactory에서 UserDAO객체를 생성하는 것 자체가 불가능해져 버렸다.

일반적으로 Singleton Pattern에는 다음과 같은 문제가 있다.
- private 생성자를 가지고 있기 때문에 상속할 수가 없다.
  Singleton클래스는 오직 자신만이 자기 오브젝트를 만들도록 제한하는 것이다. private 생성자를 가진 클래스는 다른 public 생성자가 없다면 상속이 불가능하게 된다.
- Singleton은 테스트하기가 힘들다.
  Singleton은 만들어지는 방식이 제한되어 있기 때문에 테스트에서 사용하는 목(Mock) 오브젝트 등으로 대체하여 테스트하기가 힘들다. 
- 서버환경에서는 Singleton이 하나만 만들어 지는 것을 보장하지 못한다.
   서버에서 클래스로더를 어떻게 구성하고 있느냐에 따라서 Singleton 클래스임에도 불구하고 하나 이상의 오브젝트가 만들어 질 수 있다. 여러개의 JVM이 분산되서 설치가되는 경우에도 각각 독립적으로 오브젝트가 생기기 떄문에 Singleton으로서의 가치가 떨어진다. (여러개의 Tomcat 서버)

위에 서버 환경에 따라 Singleton이 하나만 만들어 지는 것을 보장하지 못하는 문제가 Singleton Pattern에 문제점 중 하나였는데 Spring은 Singleton Registry라는 기능을 제공하여 이를 보완한다.
Singleton Registry의 장점은 스태틱 메소드와 private 생성자를 사용해야 하는 비정상적인 클래스가 아니라 평범한 자바 클래스를 Singleton으로 활용하게 해 준다는 점이다.

Spring의 SingtonRegistry 덕분에 Singleton으로 사용될 클래스라도 public 생성자를 가질 수 있다. 
=> 이 말은 테스트 환경에서 자유롭게 오브젝트를 만들 수 있고, 테스트를 위한 목(Mock) 오브젝트로 대체하는 것도 간단하다.

Spring은 IoC컨테이너일 뿐만 아니라, 고전적인 Singleton Pattern을 대신해서 Singleton을 만들고 관리해주는 Singleton Registry라는 점을 기억해 두자.
저작자 표시




이전글 Spring Singleton환경에서의 주의사항
다음글 ApplicationContext의 동작방식

목록