Programming-[Backend]/Spring

[스프링 기초] 11. 싱글톤(Singleton)

컴퓨터 탐험가 찰리 2021. 6. 30. 14:18
728x90
반응형

1. 싱글톤 개념

Spring으로 다른 애플리케이션을 개발할 수도 있지만, 보통은 웹 애플리케이션을 개발한다. 그리고 웹 애플리케이션은 보통 여러 고객이 동시에 요청을 한다. 요청때마다 계속 객체가 생성되고 소멸되는 형태인데, 이렇게 되면 메모리 낭비가 심하다.

요청 시마다 CreatureServiceImpl 인스턴스가 new 키워드를 통해 새로 생성되는 구조이다.

그러나 순수한 자바가 아닌 스프링을 이용하면 클래스의 인스턴스가 1개만 생성되어 그것을 공유해서 사용하게 된다. 이러한 방식을 싱글톤 패턴이라고 한다.

싱글톤 적용 기본 원리

이러한 싱글톤 방식을 구현하기 위해서, 자바의 private 접근 지정자 방식을 활용한다. 어떤 클래스가 호출되었을 때 적용되는 생성자를 private으로 처리해서 외부에서 해당 클래스의 인스턴스가 만들어지는 것을 막는다. 이렇게 해서 new 키워드를 통해서 인스턴스가 만들어질 수 없고, 오직 설정 정보를 갖고 있는 AppConfig.class에서 인스턴스들이 생성되게 하는 것이다.


static 키워드를 이용해서 자바 실행 시에 클래스의 인스턴스를 미리 static 영역에 생성해놓는 방식을 사용한다. 그리고 생성은 못하지만, 조회는 되도록 public으로 조회 메서드를 열어놓는다.

테스트 코드의 결과 출력 콘솔창을 확인해보면, 주소까지 완전히 같은 인스턴스를 참조하는 것을 확인할 수 있다. 이러한 원리로 스프링은 인스턴스를 1개만 만드는, 싱글톤 패턴을 지킬 수 있게 되는 것이다.


 

2. 싱글톤 컨테이너, 필드 사용 주의점

 

싱글톤 컨테이너

위에서 알아본 싱글톤 패턴 코드를 통해서 스프링은 모든 객체에 싱글톤 패턴을 자동으로 적용해준다. AppConfig에 등록된 빈들에 싱글톤 패턴을 적용해두는 것이다. 그래서 스프링의 컨테이너를 싱글톤 컨테이너 라고도 한다. 다만, 위에서 알아본 단순 싱글톤 패턴 방식이 아니라, 싱글톤 패턴의 여러 단점들을 개선해놓은 것이 스프링 컨테이너라고 생각하면 된다.

스프링 컨테이너는 스프링이 자동으로 싱글톤 컨테이너를 생성해주므로, 개발자가 직접 생성자를 넣어주고, 조회를 위한 메서드를 만들어줄 필요가 없다. 기존 싱글톤 패턴 코드는 private 생성자라서 자식 클래스를 만들기도 어렵고, 유연성도 떨어지는 문제가 있는데 이것도 해결해준다. 그리고 스프링을 이용하지 않고 조회를 위한 메서드를 직접 만들어 사용하는 것은, DI, OCP를 위반하게 된다. 싱글톤 패턴의 컨테이너에서 메서드를 사용하기 위해서는 인터페이스를 통해 SingletonService.print()와 같이 사용할 수 없고, 구현체를 직접 불러와서 SingletonServiceImpl.print()와 같이 사용해야 되기 때문이다. 이러한 객체 지향적 코드를 유지하는 것도 스프링의 싱글톤 컨테이너가 도와주게 된다.

필드 사용 시 주의점

싱글톤 컨테이너 사용 시, 특정 클라이언트에 의존적인 필드가 있으면 안된다. 또한 특정 클라이언트가 값을 변경하면 안되고, 가급적 읽기만 가능해야 한다. 그리고 상태값이 유지되는 필드가 있으면 안된다. 상태값이 유지되는 필드가 있으면 아래 코드와 같은 에러가 발생할 수 있다.

상태값이 유지되는 것을 stateful 하다고 하고, 유지되지 않는 것을 stateless 하다고 한다. 아래는 stateful한 코드로써, 상태를 유지하는 price 필드를 만들어 두고, 사용자로부터 주문(order)이 올 때마다 price 필드값에 사용자가 입력한 price값을 저장한다.

코드고치기

A 사용자가 10000원을 주문했다고 가정한다. 그리고나서 주문 금액을 조회하려는 사이에, B 사용자가 20000원을 주문하면 price값이 20000으로 바뀐 상태가 되어버려서 A 사용자가 의도한 본인의 주문 금액을 조회하지 않고 B 사용자의 주문 금액이 표시되어 버린다. 이렇게 실무에서 큰 문제가 발생할 수 있고, 테스트로 미리 생각하기도 어려운 문제가 발생할 수 있으므로 상당히 주의해야 한다.

이런 문제를 마주하지 않기 위해서, 무상태(stateless)로 설계해야 한다. 지역변수, 파라미터, ThreadLocal 등을 사용해야한다. 공유하는 price 필드값을 업데이트하는 형태가 아니라, order 메서드 자체에서 지역변수인 price를 리턴해주고, 그 값으로 조회하면 된다.

 


 

3.@Configuration과 싱글톤

 

자바 코드에 의한 싱글톤 패턴 적용 제약

스프링은 싱글톤 컨테이너를 통해 싱글톤 패턴을 유지할 수 있었다. 그러나 컨테이너의 생성 코드를 보면, 한 클래스가 다른 클래스를 불러올 때마다 new 키워드를 통해 구현체를 생성하므로 의존관계에 의하여 생성되는 클래스는 여러 개의 인스턴스를 생성하게 되는 것을 확인할 수 있다. 이렇게 되면 싱글톤 패턴이 지켜지지 않는다.

AppConfig.class에서 planService, creatureService를 조회하면 getCreatureRepository가 불리면서 new CreatureRepositoryAnimals() 코드로 자바가 인스턴스를 새롭게 생성해버린다. 이렇게 되면 싱글톤 패턴이 깨지게 될 것이다.

테스트를 위해서 각 @Bean 내부에 빈이 호출되었다는 문장을 출력하도록 했다. 실제로 테스트를 해보자.


3개의 빈을 호출하면, getCreatureRepository가 3번 호출되는 것을 확인할 수 있다. 싱글톤 패턴이 깨진 것이다.

@Configuration에서의 바이트코드 조작

자바 코드가 그대로 실행되면서 싱글톤 패턴이 깨지는 문제를 해결하기 위해서, 스프링은 바이트코드 조작이라는 기술을 사용한다. 내가 작성한 설정 정보를 그대로 컨테이너에 담는 것이 아니라, 해당 객체를 상속받은 객체를 컨테이너에 등록하는 것이다.

그리고 정확한 코드를 알 수 없지만, 컨테이너에서 빈을 생성할 때 이미 컨테이너에 있는 빈이라면 해당 빈을 그대로 반환하고, 없으면 새로 생성하는 방식의 코드가 적용되어 있을 것이라 추측할 수 있다.


이러한 방식을 적용하기 위한 방법은, 설정 정보 파일에 @Configuration 어노테이션을 붙여주는 것이다. 설정 정보 파일에는 @Configuration 어노테이션을 반드시 달아주도록 해야한다! 이렇게 하지 않으면 싱글톤 패턴이 깨지게 된다.


@Configuration 적용 시, 각 인스턴스가 한번씩만 호출되어 싱글톤 패턴이 지켜진다.


참조

1. 인프런_스프링 핵심 원리 기본편_김영한 님 강의
www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

728x90
반응형