※ 본문은 인프런 김영한님의 스프링 강의를 바탕으로 학습한 내용을 기록한 글입니다.
오개념이 있다면 댓글로 알려주세요!
스프링 컨테이너가 아닌, 순수한 DI 컨테이너 AppConfig(DI를 설정한 자바 클래스라고 가정)는 클라이언트가 요청을 할 때마다 새로운 객체를 생성하여 DI를 수행한다. 즉, 초당 1000개의 요청이 있다면 1000개의 객체가 생성된 후 소멸되며, 이는 결국 메모리 낭비를 유발한다.
이를 해결하기 위해 무수히 많은 요청이 있더라도 객체를 딱 1개만 생성한 후 해당 객체를 공유하도록 설계하는 싱글톤 패턴을 활용할 수 있다.
[ 1 ] 싱글톤 패턴이란?
싱글톤 패턴은 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다. 즉, 자바에서는 하나의 JVM에 특정 객체의 인스턴스가 딱 1개만 생성된다는 의미이다.
싱글톤 패턴을 구현하는 방법은 매우 다양하지만, 해당 게시글은 가장 기초적인 방법을 활용하였다.
(1) 우선, static을 통해 해당 클래스를 클래스로더가 로딩할 때 객체를 생성하도록 한다.
(2) 다른 객체를 생성하지 못하도록 기본 생성자의 접근제어자를 private로 설정하고, 유일한 객체를 getInstance() 메서드로 받을 수 있도록 한다.
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
private SingletonService() {
// 생성자의 접근제어자를 private로 설정함으로써 getInstance() 로만 인스턴스를 얻을 수 있도록 한다.
// 이때 instance는 static 변수이므로 유일한 인스턴스이다.
// 해당 로직으로 싱글톤 패턴을 적용할 수 있다.
}
}
위 예제의 getInstance()는 항상 같은 객체를 반환하기 때문에 해당 클래스는 싱글톤 패턴을 보장한다.
[ 2 ] 싱글톤 패턴의 문제점
싱글톤 패턴을 활용함으로써 많은 요청이 있더라도 유일한 객체를 공유하며 메모리 낭비를 방지할 수 있게 되었다. 하지만, 싱글톤 패턴을 활용하면 많은 문제가 발생할 수 있다.
(1) 위 예제에서 볼 수 있듯이 구현 코드 자체가 길어진다.
(2) DI 설정 시 구현 클래스의 getInstance() 메서드를 사용해야 하므로 인터페이스가 아닌 구현 클래스에 의존하게 된다. 즉, DIP를 위반한다.
(3) 구현 클래스에 의존하게 되므로 구현 클래스를 변경할 때마다 코드 수정이 불가피하다. 즉, OCP를 위반한다.
(4) 테스트 코드 작성, 내부 속성 변경 및 초기화가 어렵다.
스프링 컨테이너는 이러한 싱글톤 패턴의 문제들을 모두 해결할 수 있다.
[ 3 ] 스프링이 싱글톤을 보장하는 방법
스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 인스턴스를 싱글톤으로 관리한다. 즉, DIP, OCP, 테스트, private 생성자로부터 자유롭게 싱글톤을 보장할 수 있음을 의미한다.
이것이 어떻게 가능할까?
스프링은 @Configuration 애노테이션이 붙은 설정 클래스(AppConfig.java 라고 가정)의 바이트코드를 조작하는 라이브러리를 사용한다. 스프링 컨테이너는 설정 클래스도 스프링 빈으로 등록하는데, 설정 클래스에 @Configuration 애노테이션이 있다면 CGLIB이라는 바이트코드 조작 라이브러리를 사용하여 설정 클래스를 상속받는 임의의 클래스를 만들고, 해당 임의 클래스를 스프링 빈으로 등록한다. 임의의 클래스는 싱글톤을 보장하도록 AppConfig의 바이트코드를 조작하여 작성되어 있고, 이를 통해 스프링은 스프링 빈의 싱글톤을 보장할 수 있다.
※ 스프링은 @Configuration 애노테이션이 붙은 설정 클래스의 @Bean들에 대해서만 싱글톤을 보장한다. 물론 @Configuration 애노테이션이 없는 설정 클래스의 @Bean들도 스프링 빈으로 등록되지만, 설정 클래스에 @Configuration 애노테이션이 없다면 싱글톤이 보장되진 않는다.
그러므로 스프링 설정 클래스에는 @Configuration 애노테이션을 사용하도록 하자.
[ 4 ] 싱글톤 패턴 사용 시 주의점
멀티스레드 환경에서 싱글톤 패턴 사용 시 '필드'에 공유값을 설정하면 큰 문제가 발생할 수 있다. 예를 들어, 싱글톤 패턴을 활용한 클래스의 필드에 int price; 있고, A 스레드와 B 스레드가 동시에 해당 클래스에 접근한다고 가정하자 .
- A 스레드에서 멤버변수 price를 10000으로 초기화
- A 스레드가 price를 초기화 하자마자 B 스레드가 멤버변수 price를 20000으로 초기화
- A 스레드와 B 스레드는 싱글톤 패턴을 활용한 클래스를 사용하고 있으므로 같은 인스턴스를 참조하고 있다. 그러므로 A 스레드가 price를 조회할 때 10000이 아닌 20000을 얻게 된다.
해당 문제는 심각한 문제를 유발할 수 있으므로 싱글톤 객체는 상태를 유지(stateful)해서는 안되고, 반드시 무상태(stateless)로 설계해야 한다. 무상태(stateless)로 싱글톤 객체를 설계하기 위해서는 필드 대신 지역변수, 파라미터, ThreadLocal 등을 사용하도록 하자.
'Programming > Spring' 카테고리의 다른 글
[Spring] 여러가지 DI 방법, 의존관계 자동 주입(@Autowired) (1) | 2023.04.10 |
---|---|
[Spring] 컴포넌트 스캔 (1) | 2023.03.24 |
[Spring] 스프링 컨테이너(Spring Container) (1) | 2023.03.07 |