본문 바로가기

Spring

싱글톤과 스프링

싱글톤이란 무엇일까요 ?

이를 알기 위해선 먼저 오브젝트의 동일성과 동등성에 대해 아셔야합니다.

게시글 바로가기 > 동일성, 동등성


싱글톤이란 해당 클래스의 인스턴스가 하나만 만들어지는, 전역변수와 같은 것을 말합니다.

보통 객체가 여러개 있는 것 보다 하나만 있어야 하는 경우에 사용합니다. 예를 들면 DB 커넥션 풀이 있겠네요.


싱글톤 얘기가 갑자기 나온 이유는, 스프링이 모든 빈 들을 싱글톤으로 관리하기 때문입니다.

확인부터 해보겠습니다.

ApplicationContext context = 
        new GenericXmlApplicationContext("applicationContext.xml");
CarDAO carDao1 = context.getBean("carDAO",CarDAO.class);
CarDAO carDao2 = context.getBean("carDAO",CarDAO.class);

System.out.println(carDao1);
System.out.println(carDao2);
System.out.println(carDao1==carDao2);

carDAO라는 이름을 가진 빈을 2번 가져와서 변수에 넣어 출력했고, 비교했습니다.

보시다시피 두 오브젝트는 동일합니다. 설정파일에서 빈을 가져올 때마다 오브젝트를 새로 생성하지 않는다는 의미입니다.


스프링에서는 왜 위와 같이 빈을 싱글톤으로 만드는 걸까요?

스프링이 주로 적용되는 대상이 대부분 자바 엔터프라이즈 기술을 사용하는 서버 환경이기 때문입니다.

서버 환경의 특징이란, 많은 사람이 이용하고 그에 따라 요청의 횟수도 많습니다.


위에서 내보인 CarDAO 클래스를 예로 들어보겠습니다.

CarDAO 클래스에는 데이터 조회의 기능을 하는 selectXXXData 형태의 메서드들이 들어가 있었습니다.

이 CarDAO 클래스는 웹 사이트에서 자동차를 조회하는 페이지 마다 다 사용되겠지요.

이러한 CarDAO 클래스를 클라이언트에서 요청이 올 떄마다 새로 만들어서 사용한다고 가정해 보면

10번 요청하면 10개의 오브젝트가 만들어지고, 100번 요청하면 100개의 오브젝트가 만들어집니다.

만약 사이트에서 해당 페이지에 초당 100번의 요청이 있다고 가정하면?

1분만 지나도 약 6,000개, 10분이면 약 6만개의 오브젝트가 만들어지게 됩니다.

아무리 가비지 컬렉션의 성능이 좋아졌다고 해도, 이는 너무 부하가 가는 작업입니다.

그래서 스프링은 각각의 빈들을 싱글톤으로 관리하고, 여러 스레드에서 이를 공유하여 사용할 수 있게 해주는 겁니다.



싱글톤 패턴과의 차이점

싱글톤 패턴이란 GoF가 소개한 디자인 패턴 중 하나인데, 사실 그리 좋은 패턴만은 아닙니다. 조심해서 사용해야 합니다.

자바에서 싱글톤을 구현하는 방법은 보통 아래와 같습니다.

1) 클래스밖에서 오브젝트를 생성하지 못하도록 생성자를 private로 만든다

2) 생성된 싱글톤 오브젝트를 저장할 수 있도록 자신과 같은 타입의 스태틱 필드를 정의한다

3) 스태틱 메서드인 getInstance()를 만들고 최초 호출 시 한번만 오브젝트가 만들어지게 한다.

4) 오브젝트가 한번 만들어지고 난 후에는 getInstance 호출 시 이미 만들어진 오브젝트를 넘겨준다.

CarDAO을 싱글톤 패턴으로 만들어보겠습니다.

public class CarDAO {
    private static CarDAO carDAO;
    
    private CarDAO(){}
    
    public static synchronized CarDAO getInstance(){
        if(carDAO==null){
            carDAO = new CarDAO();
        }
        return carDAO;
    }
}

이를 DaoFactory에서 한번 사용해.. 볼랬는데,private 생성자 때문에 생성부터가 안되네요.


public class DaoFactory{
    public CarDAO carDAO(){
        CarDAO carDao = new CarDAO();
        carDao.setDbConnection(dBConnection());
         
        return carDao;
    }
     
    public DBConnection dBConnection(){
        return new MySqlDBConnection_real();
    }
}

기존 DaoFactory의 소스입니다.

static 메서드인 getInstance를 호출하여 오브젝트를 받아와 싱글톤으로 관리할 수 있긴 하지만

또 하나의 문제점이 private 생성자 때문에 상속이 안됩니다.. 객체지향의 꽃인 상속과 다형성이 빠지면..

물론 이것도 부모 자식쪽에서 잘 설계해서 다른 생성자를 호출해주고.. 이런 과정을 거치면 어찌어찌 구현은 가능합니다.

하지만 복잡하고 번거롭고, 위험하기도 한 작업입니다. 싱글톤 패턴은 구현이 어렵고, 테스트 하기도 어렵습니다.



싱글톤 레지스트리

이에 비해 스프링의 applicationContext는 아주 효율적인 싱글톤 레지스트리(싱글톤을 관리한다는 의미) 입니다.

스프링은 앞서 구현한 싱글톤 처럼 private 생성자, static 메서드 등을 사용하지 않고

평범한 자바클래스를 싱글톤 방식으로 만들어 관리되게 할 수 있습니다.

public 생성자를 사용할 수 있으며, 테스트도 용이합니다. 일반적인 자바 클래스와 동일하게 사용이 가능합니다!

게다가 추가 옵션으로 싱글톤이 아니게 관리할 수도 있습니다.

역시.. 대단합니다 스프링. 갓프링 만세 ㅋㅋ


마지막으로 싱글톤으로 관리되는 빈에서 주의해야 할 점을 짚고 넘어가겠습니다.

싱글톤 특성상 여러 스레드가 동시에 접근해서 사용하는 오브젝트이므로, 상태관리에 주의를 기울여야 합니다.

상태를 갖고 있지 않은 무상태(stateless) 형태로 만들어져야 합니다.

각 요청 정보나, 생성 정보는 로컬변수, 파라미터, 리턴 값 등을 이용하도록 해줍니다.

절대 아래와 같은 코드가 나와선 안됩니다.

public class CarDAO {
    DBConnection dbConnection;

    public void setDbConnection(DBConnection dbConnection) {
        this.dbConnection = dbConnection;
    }
    
    Connection conn; // 위험
    List list; // 위험

    public List selectSUVData() throws Exception{
        conn = dbConnection.getConnection();
        
        list = // SUV 정보 쿼리
        return list;
    }
 
    public List selectSedanData() throws Exception{
        conn = dbConnection.getConnection();
        
        list = // 세단 정보 쿼리
        return list;
    }
}

각 메서드는 로컬 변수가 아닌 인스턴스 변수를 사용해서 데이터를 조회하고, 돌려주고 있습니다.

CarDAO는 여러 스레드가 공유해서 쓰는 오브젝트입니다.

발생할 수 있는 예로, SUV 정보를 보는 페이지에 들어갔는데 다른 사용자가 세단 정보를 보는 페이지를 들어가면

SUV 정보를 보려던 사용자는 세단 정보를 보게 되버립니다. 

인스턴스 변수를 사용했기 때문에 공유하여 사용하는 다른 사용자가 값을 바꾸어버린 셈이 됩니다.

리스트가 아니라 insert, update 등의 작업을 하는 메서드였으면 더 심각한 결과를 초래했겠지요.


이러한 이유 때문에 싱글톤으로 관리되는 스프링 빈은 꼭 stateless 방식으로 만들어야 합니다.!


이상 스프링 싱글톤에 대한 포스팅을 마치겠습니다.

읽어주셔서 감사합니다.

'Spring' 카테고리의 다른 글

템플릿 콜백 패턴  (0) 2016.06.28
테스트와 JUnit  (3) 2016.06.27
스프링 DI  (0) 2016.06.22
DI란?  (0) 2016.06.16
스프링 dataSource 예제 (Mysql, Oracle)  (0) 2016.06.09