[스프링 핵심 원리] 5. 싱글톤 컨테이너

건우·2025년 9월 5일

Back-end / Java, Spring

목록 보기
5/14
post-thumbnail

왜 스프링은 기본이 싱글톤일까? (그리고 @Configuration의 진짜 역할)

이 챕터는 단순히 “싱글톤이 좋다”가 아니라
웹 환경에서 왜 싱글톤이 기본 전략인지를 이해하는 파트였다.

그리고 마지막에 나오는 CGLIB 이야기가 은근히 재밌다.


1. 웹 애플리케이션과 객체 생성 비용

스프링은 기본적으로 웹 애플리케이션을 위한 기술에서 출발했다.

웹 환경의 특징은 이거다.
동시에 여러 사용자가 요청을 보낸다.

만약 우리가 만들었던 순수 DI 컨테이너(AppConfig)가
요청 올 때마다 객체를 새로 만든다면?

  • 초당 100 요청
    → 초당 100개 객체 생성
    → 계속 생성 & 소멸

이건 그냥 메모리 낭비다.
그래서 해결 방법은 단순하다.

“객체를 한 번만 만들고, 공유하면 되지 않나?”

그게 바로 싱글톤이다.


2. 싱글톤 패턴이란?

싱글톤 패턴은

“객체를 딱 1개만 생성하도록 보장하는 디자인 패턴”

전통적인 방식은 이런 느낌이다.

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    private SingletonService() {}

    public static SingletonService getInstance() {
        return instance;
    }
}
  • 생성자는 private
  • 외부에서 new 못함
  • getInstance()로만 접근

근데 문제 많다
강의에서 강조한 단점이 꽤 많다.

  • 구현 코드가 지저분하다
  • DIP 위반하기 쉽다
  • 테스트 어렵다
  • 유연성 떨어진다
  • 상속 어렵다

결론적으로 좋은 객체지향 설계랑 안 맞는다.
그래서 이걸 직접 쓰는 건 점점 안티패턴 취급을 받는다.


3. 스프링의 싱글톤 컨테이너

스프링은 다르게 접근한다.

“패턴을 직접 쓰지 말고, 컨테이너가 대신 관리해줄게.”

스프링 컨테이너는

  • 객체를 한 번만 생성하고
  • 그 인스턴스를 계속 재사용한다

이걸 싱글톤 레지스트리라고 한다.

그리고 우리가 지금까지 만든 @Bean 객체들은
기본적으로 전부 싱글톤이다.

여기서 중요한 차이:

  • 우리가 직접 싱글톤 코드를 안 짜도 된다
  • DIP, OCP를 해치지 않는다
  • 테스트하기도 수월하다

이게 스프링이 잘 설계된 부분이라고 느꼈다.


4. 싱글톤의 진짜 주의점 (이게 더 중요함)

싱글톤은 객체를 공유한다.

즉,
여러 사용자가 하나의 객체를 동시에 사용한다.
그래서 절대 하면 안 되는 게 있다.

상태를 가지면 안 된다 (stateful 금지)

예를 들어 이런 코드가 있다면

private int price; // 공유 필드

ThreadA가 10000원 넣고
ThreadB가 20000원 넣으면

결과가 꼬일 수 있다.

사용자A 주문금액이
20000원으로 나올 수도 있다.

그래서 원칙은 이거다.
스프링 빈은 무조건 “무상태(stateless)“로 설계하자.

  • 특정 사용자 값 저장하지 말 것
  • 공유 필드 쓰지 말 것
  • 값은 파라미터/지역변수로 처리

이건 실무에서도 진짜 중요하다고 느껴진다.


5. @Configuration 없이도 싱글톤이 보장될까?

이 파트가 제일 흥미로웠다.

AppConfig 코드에서 이런 상황이 있었다.

@Bean
public MemberService memberService() {
    return new MemberServiceImpl(memberRepository());
}

@Bean
public OrderService orderService() {
    return new OrderServiceImpl(memberRepository(), discountPolicy());
}

겉으로 보면 memberRepository()가
• 컨테이너 등록할 때 1번
• memberService에서 1번
• orderService에서 1번

총 3번 호출될 것 같지 않나?
그럼 MemoryMemberRepository 인스턴스도 3개 생겨야 맞다.

근데 실제로는 1개만 생성된다.

왜?


6. @Configuration과 CGLIB의 마법

비밀은 여기에 있다.

@Configuration
public class AppConfig { ... }

스프링은 @Configuration이 붙은 클래스를 그냥 쓰지 않는다.

CGLIB라는 바이트코드 조작 라이브러리를 사용해서
AppConfig를 상속한 가짜 클래스를 하나 만든다.

예를 들어 클래스 이름이 이런 식으로 바뀐다.

AppConfig$$EnhancerBySpringCGLIB

이 가짜 클래스 안에서 이런 동작을 한다.

@Bean 메서드를 호출하면
이미 빈이 존재하는지 확인하고
있으면 기존 빈 반환
없으면 생성해서 등록 후 반환

그래서 결과적으로 싱글톤이 보장된다.


7. @Bean만 쓰고 @Configuration 안 쓰면?

여기서 중요한 포인트.

@Configuration을 빼고
@Bean만 쓰면?

→ CGLIB이 동작하지 않는다.
→ memberRepository()가 실제로 여러 번 호출된다.
→ 인스턴스가 여러 개 생성된다.

즉, @Bean만으로는 싱글톤을 보장하지 않는다.

그래서 실무에서는
스프링 설정 클래스에는 무조건 @Configuration을 붙인다.

라고 이해하면 된다.


8. 내가 이 챕터에서 얻은 핵심

  1. 스프링은 기본이 싱글톤이다.
  2. 싱글톤 객체는 반드시 무상태(stateless)로 설계해야 한다.
  3. @Configuration은 단순 설정 표시가 아니라
    싱글톤 보장을 위한 바이트코드 조작 트리거다.

9. 한 줄 정리

스프링은 “싱글톤 패턴을 쓰는 게 아니라”
싱글톤을 안전하게 관리해주는 컨테이너다.

그리고 @Configuration은
그 싱글톤을 보장하는 숨은 핵심 장치다.


출처
스프링 핵심 원리 - 기본편 (김영한, 인프런, 2020)

0개의 댓글