매번 요청이 들어올때마다 컨테이너에서
새로운 객체를 생성할 필요가 있을까?
이번 글은 싱글톤 컨테이너에 대해 정리한 글입니다.
만약, 매 요청마다 컨테이너가 새로운 객체를 만들어 의존성 주입을 해주면 어떻게 될까요?
매 요청마다 새로운 객체를 생성하면 힙 메모리를 사용하게 되어 자원이 소모됩니다.
소량의 요청이라면 큰 문제가 없지만, 만약 100만 개의 요청이 들어온다면 상황이 달라집니다. 이 경우 100만 개의 객체가 힙 영역에 올라갈 수 있을지 의문이며, 객체 크기에 따라 메모리 부족이나 가비지 컬렉터의 부담이 커질 가능성이 있습니다.
그렇다면, 매번 새로운 객체를 만드는 것이 아닌, 한번 만든 객체를 재사용하면 되지 않을까요?
싱글톤 컨테이너
스프링 컨테이너는 싱글톤 패턴을 적용하지 않고, 객체 인스턴스를 싱글톤으로 관리합니다.
애플리케이션 내에서 객체를 단 하나만 생성하고 공유하여, 메모리 효율을 높이고 객체 생성을 최소화하는 역할을 합니다.
중요한 점은 싱글톤 패턴을 적용하지 않고, 객체 인스턴스를 싱글톤으로 관리한다는 것입니다.
싱글톤 패턴은 무엇일까요?
싱글톤 패턴은 한 클래스의 인스턴스를 하나만 생성하고, 이를 공유하도록 하는 디자인 패턴입니다. 즉, 동일한 객체를 여러 번 생성하지 않고, 최초 생성된 객체를 재사용하는 방식입니다.
싱글톤 패턴으로 어떻게 한번 생성한 객체를 재사용할 수 있는지 코드로 알아보겠습니다.
싱글톤 패턴 예시 코드
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
코드를 살펴보겠습니다.
먼저, static 영역에 객체 instance를 미리 하나 생성합니다. 이 객체 인스턴스는 필요한 경우 오직 getInstance() 메서드를 통해서만 조회할 수 있습니다. 이 메서드를 호출시 항상 같은 인스턴스(static)를 반환합니다. 또한, 1개의 객체 인스턴스만 존재해야 하므로, 생성자를 private으로 막아서 혹시라도 외부에서 객체 인스턴스가 생성되는 것을 막습니다.
정말, 같은 객체를 반환하는지 테스트해보겠습니다.
싱글톤 객체 테스트 코드
아래 코드는 각 객체의 주소값을 비교해서 일치하는지 확인합니다.
package org.example;
public class Main {
public static void main(String[] args) {
Singleton objectA = Singleton.getInstance();
Singleton objectB = Singleton.getInstance();
if(objectA == objectB) {
System.out.println("두 객체는 같은 객체입니다.");
}
}
}
출력 결과
테스트를 통해서 두 객체가 똑같은 객체임이 증명 되었습니다.
왜 싱글톤 컨테이너는 싱글톤 패턴을 적용하지 않을까요?
싱글톤 패턴은 한번 만든 객체를 공유함으로써 효율적으로 사용할 수 있다는 점은 장점이지만, 다양한 단점도 존재합니다.
싱글톤 패턴의 단점
싱글톤 패턴은 아래와 같은 단점들이 존재합니다.
- 싱글톤 패턴을 구현하는 코드 자체가 많이 들어갑니다. (코드 비용 증가)
- 의존관계상 클라이언트가 구체 클래스에 의존한다. DIP를 위반합니다. (SOLID 위반)
- 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높습니다. (SOLID 위반)
- 테스트하기 어렵습니다.
- 내부 속성을 변경하거나 초기화 하기 어렵습니다.
- private 생성자로 자식 클래스를 만들기 어렵습니다.
- 결론적으로 유연성이 떨어집니다.
- 안티패턴으로 불리기도 합니다.
싱글톤 컨테이너는 어떻게 동작할까요?
싱글톤 컨테이너는 싱글톤 패턴의 단점을 해결하고자 싱글톤 패턴을 적용하지 않고, 객체 인스턴스를 싱글톤으로 관리해줍니다.
이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라고 부릅니다.
싱글톤 컨테이너를 사용하려면 어떻게 해야할까요?
@Configuration이라는 어노테이션을 사용하면 됩니다.
정말, Spring은 싱글톤 컨테이너를 사용해서 같은 객체를 반환해주는지 테스트해보겠습니다.
싱글톤 컨테이너 테스트 코드
이번에도 싱글턴 컨테이너로 부터 얻은 두 객체의 주소값 비교를 통해 동일한 객체인지 확인해보겠습니다. 테스트 코드는 아래와 같습니다.
package com.example.spring_study_test.singleton;
import static org.junit.jupiter.api.Assertions.*;
import com.example.spring_study_test.service.TeamService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@SpringBootTest
public class SingletonTest {
@Configuration
static class BaseConfig {
@Bean
public TeamService teamService() {
return new TeamService();
}
}
ApplicationContext ac = new AnnotationConfigApplicationContext(BaseConfig.class);
@Test
void givenTwoSingletonObj_whenEquals_thenTrue() {
//given
TeamService teamService1 = ac.getBean("teamService", TeamService.class);
TeamService teamService2 = ac.getBean("teamService", TeamService.class);
//when
boolean result = teamService1 == teamService2;
System.out.println("teamService1 = " + teamService1);
System.out.println("teamService2 = " + teamService2);
//then
assertTrue(result);
}
}
출력 결과
두 객체의 주소값이 @4fcc529로 일치하는 것을 확인할 수 있습니다.
싱글톤 패턴의 단점을 보완하기 위해 컨테이너는 싱글톤 패턴 없이 싱글톤 객체를 제공했습니다. 그렇다면 싱글톤 객체 자체에는 문제가 없을까요?
싱글톤 사용시 주의점
싱글톤 방식은 하나의 객체를 생성해서 공유되는 객체이기 때문에 , 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안됩니다.
왜 싱글톤 객체는 stateful하게 설계하면 안될까요?
아래와 같은 문제점이 존재합니다.
- 여러 클라이언트가 동시에 싱글톤 객체를 사용하면 동시성 문제로 인해 데이터 정합성이 깨질 수 있습니다.
- 상태를 가진 싱글톤 객체는 다중 스레드 환경에서 스레드 안전성을 보장하지 못해 데드락이나 레이스 컨디션과 같은 문제가 발생할 수 있습니다.
- 싱글톤 객체가 상태를 가진 경우, 테스트 간 상태가 공유되어 독립적인 테스트를 수행하기 어렵습니다.
정리
- 스프링 컨테이너는 싱글톤 패턴을 적용하지 않고, 객체 인스턴스를 싱글톤으로 관리합니다.
- 싱글톤 방식은 하나의 객체를 생성해서 공유되는 객체이기 때문에 , 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안됩니다.
'Spring' 카테고리의 다른 글
@Transactional 원리 (0) | 2025.02.25 |
---|---|
트랜잭션 전파 다양한 옵션 (0) | 2025.02.17 |
트랜잭션 전파 REQUIRES_NEW 활용 (0) | 2025.02.15 |
트랜잭션 전파(REQUIRES_NEW) (0) | 2025.02.14 |
트랜잭션 전파(REQUIRED) ROLLBACK 편 (0) | 2025.02.13 |