웹 어플리케이션과 싱글톤
스프링은 기업용 온라인 서비스 개발을 지원하기 위해 탄생한 프레임워크다.
물론 스프링으로 웹 어플리케이션 뿐 아니라 다양한 어플리케이션을 개발할 수 있지만,
스프링은 웹 어플리케이션을 개발하는 데 최적화되어 있다.
웹 어플리케이션의 특징이 무엇이냐 하면, 다수의 클라이언트가 동시에 요청을 한다는 점이다.
즉 스프링이 웹 어플리케이션 개발에 최적화되어 있다는 얘기는, 다수의 요청을 동시에 처리하는데 특화되어 있다는 말과 같은 의미다.
다수의 요청을 동시에 처리할 때, 각 요청에 대해 일일이 객체를 생성해서 응답하면 메모리 공간이 크게 낭비될 것이다.
당장 요청을 100개만 처리한다고 해도 객체 100개가 새로 생성되고 소멸된다는 이야기인데, 결코 효율적이라 할 수 없다.
이 때 유용한 것이 싱글톤 패턴이다. 싱글톤 패턴을 이용하면 다수의 요청에 대해 하나의 객체를 공유하여 처리할 수 있다.
그리고 스프링 역시 싱글톤 패턴을 이용해 다수의 요청을 효율적으로 동시에 처리한다.
싱글톤 패턴
싱글톤 패턴이란, 클래스 인스턴스가 단 1개만 생성되는 것을 보장하는 디자인 패턴을 말한다.
만드는 방법은 간단하다.
- static 영역에 클래스 인스턴스를 하나 생성한다.
- 생성한 인스턴스를 리턴하는 메서드를 하나 작성한다.
- 생성자를 private로 선언해 외부에서 새로운 인스턴스를 생성하지 못하도록 막는다.
이를 코드로 작성한 예시는 다음과 같다.
SingletonService.java
package hdxian.hdxianspringcore.singleton;
public class SingletonService {
// 관례상 싱글톤 인스턴스 이름은 instance.
// static으로 선언 -> 클래스 레벨에서 로드됨.
private static final SingletonService instance = new SingletonService();
private SingletonService() {
// 생성자를 private로 선언해서 외부에서 SingletonService 객체를 생성하지 못하도록 한다.
}
public static SingletonService getInstance() {
return instance;
}
public void logic() {
System.out.println("singletonService의 로직 실행");
}
}
instance라는 이름의 참조변수 하나를 멤버로 선언하고, 객체를 생성해서 하나 집어넣는다.
SingletonService() 생성자는 private로 선언하여 외부에서 접근하지 못하도록 한다.
getInstance() 메서드를 선언하여 instance를 리턴하도록 작성한다.
그 외에 해당 인스턴스로 실행할 작업들(logic() 등)을 별도로 작성한다.
해당 클래스를 바탕으로 테스트를 돌려보면, 같은 객체임을 알 수 있다.
@Test
@DisplayName("싱글톤 패턴 적용 사례 테스트")
void singletonServiceTest() {
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
System.out.println("singletonService1 = " + singletonService1);
System.out.println("singletonService2 = " + singletonService2);
// same: == (참조 비교)
// equal: equals() (내용 비교)
assertThat(singletonService1).isSameAs(singletonService2);
}
싱글톤 패턴을 구현하는 방법은 여러가지가 있다.
예시의 방법은 클래스 로드 시점부터 인스턴스를 미리 생성해놓는 방식이고,
처음에는 인스턴스를 생성하지 않고 있다가 해당 클래스 인스턴스를 참조할 때 생성하는 지연 처리 방식도 있다.
결론적으로, 싱글톤 패턴을 적용하면 클라이언트 요청이 올 때마다 새로운 객체가 아닌 객체의 참조를 리턴하므로
훨씬 효율적으로 자원을 사용할 수 있다.
하지만 싱글톤 패턴은 좋아보이면서도, 다음과 같은 여러가지 문제점을 가지고 있다.
- 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
- 의존관계상 클라이언트가 구체 클래스에 의존한다. ( [구체 클래스].getInstace() )
- 클라이언트가 구체 클래스에 의존하므로 OCP 원칙을 위반할 가능성이 높다.
- 테스트가 어렵다.
- 내부 속성을 변경하거나 초기화하기 어렵다.
- 자식클래스를 만들기 어렵다. (private 생성자)
- 유연성이 떨어진다.
- 안티패턴으로 불리기도 한다.
그렇다면 싱글톤 패턴을 사용하는 스프링도 이러한 문제점이 있는 것일까?
다행스럽게도, 스프링 컨테이너를 이용하면 이러한 싱글톤 패턴의 문제점들을 해결하면서 싱글톤 패턴을 활용할 수 있다.