싱글톤 레지스트리는 디자인 패턴 중 하나인 싱글톤 패턴의 구현 방식을 보완한 기능으로 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 한다.
싱글톤 레지스트리를 학습하기 전, 먼저 스프링의 애플리케이션 컨텍스트와 오브젝트 팩토리로 동일한 객체를 여러 번 호출 했을 때의 결과 값을 먼저 확인해 보자.
@Configuration
public class DaoFactory {
@Bean
public UserDao userDao() {
return new UserDao(connectionMaker());
}
@Bean
public ConnectionMaker connectionMaker() {
return new DConnectionMaker();
}
}
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
}
public class UserDaoTest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao userDao1 = context.getBean("userDao", UserDao.class);
UserDao userDao2 = context.getBean("userDao", UserDao.class);
System.out.println("userDao1 = " + userDao1);
System.out.println("userDao2 = " + userDao2);
}
}
[결과]
userDao1 = com.example.tobi.dao.UserDao@5745ca0e
userDao2 = com.example.tobi.dao.UserDao@5745ca0e
public class UserDaoTest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
DaoFactory daoFactory = new DaoFactory();
UserDao userDao1 = daoFactory.userDao();
UserDao userDao2 = daoFactory.userDao();
System.out.println("userDao1 = " + userDao1);
System.out.println("userDao2 = " + userDao2);
}
}
[결과]
userDao1 = com.example.tobi.dao.UserDao@13526e59
userDao2 = com.example.tobi.dao.UserDao@2235eaab
위의 결과를 보면 스프링은 기본적으로 별다른 설정이 없다면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만들기 때문에 같은 객체를 여러번 호출해도 동일한 객체를 반환하는 것을 확인할 수 있고, 오브젝트 팩토리로 호출한 결과는 싱글톤 방식이 아니기 때문에 서로 다른 객체를 반환하고 있다는 것을 확인할 수 있다.
스프링은 태생적으로 엔터프라이즈 시스템을 위해 고안된 기술이기 때문에 서버환경에서 사용될 때 그 가치가 있다. 실제로 스프링은 대부분 서버환경에서 사용되기도 한다. 스프링이 처음 설계됐던 대규모의 엔터프라이즈 서버환경은 서버 하나당 최대로 초당 수십에서 수백 번씩 요청을 받아 처리할 수 있는 높은 성능이 요구되는 환경이었다.
그런데 클라이언트의 요청은 한두개가 아닌 대규모 시스템의 경우 초당 몇 천개의 요청을 받아야 할 때도 있다. 이럴 때마다 매번 오브젝트를 새로 만들어 사용한다면 분명 서버는 감당하기 힘든 수준이 될 것이다.
이런 점을 보완하여 한 개의 오브젝트만을 만들어서 사용할 수 있게 하는 것이 *싱글톤 패턴의 원리이자 스프링이 싱글톤으로 빈을 생성하는 이유이다.
하지만 이런 싱글톤 패턴에는 여러 가지 문제점이 있다.
private 생성자를 갖고 있기 때문에 상속할 수 없다.
싱글톤 패턴은 생성자를 private으로 제한한다. private 생성자를 가진 클래스는 객체지향의 장점인 상속과 이를 이용한 다형성을 적용할 수 없다.
싱글톤은 테스트하기 힘들다.
싱글톤은 초기화 과정에서 생성자 등을 통해 사용할 오브젝트를 직접 주입하기도 힘들기 때문에 필요한 오브젝트는 직접 오브젝트를 만들어 사용할 수 밖에 없다. 이런 경우 테스트용 오브젝트로 대체하기가 힘들다.
서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
여러 개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생기기 때문에 싱글톤으로서의 가치를 보장하지 못한다.
싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.
싱글톤의 static 메소드를 이용해 언제든지 싱글톤에 접글할 수 있어 전역 상태로 사용될 수 있다. 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는 프로그래밍 모델이다.
바로 이러한 문제점때문에 스프링은 싱글톤 레지스트리를 제공하여 직접 싱글톤 형태의 오브젝트를 만들고 관리할 수 있게 하였다.
🌟싱글톤 레지스트리는 private 생성자를 사용해야 하는 방법이 아닌 평범한 자바 클래스를 싱글톤으로 활용할 수 있게 해준다. 또한 IoC 방식의 컨테이너를 사용해서 생성과 관계설정, 사용 등에 대한 제어권을 컨테이너에게 넘기면서 손쉽게 싱글톤 방식으로 관리되게 할 수 있다. 오브젝트 생성에 관한 모든 권한은 IoC 기능을 제공하는 애플리케이션 컨텍스트에 있기 때문이다.
앞에서 코드를 통해 확인했듯이, 스프링의 애플리케이션 컨텍스트로 UserDao를 여러 번 호출 하더라도 동일한 객체의 결과를 봤을 때 이미 UserDao는 스프링 IoC를 적용하면서 싱글톤으로 만들어졌다고는 것을 알 수 있다.
또한 싱글톤 패턴과 달리 스프링이 지지하는 객체지향적인 설계 방식과 원칙, 디자인 패턴 등을 적용하는 데 아무런 제약이 없다. 스프링은 IoC 컨테이너일 뿐만 아니라, 고전적인 싱글톤 패턴을 대신해서 싱글톤을 만들고 관리해주는 싱글톤 레지스트리이다. 스프링이 빈을 싱글톤으로 만드는 것은 결국 오브젝트의 생성 방법을 제어하는 IoC 컨테이너로서의 역할이다.
[참고자료]
토비의 스프링 3.1 (Vol.1 스프링 이해와 원리)