대부분의 스프링 애플리케이션은 웹 애플리케이션이다. 웹 환경은 수많은 고객이 동시에 요청을 보내는 특성을 가진다. 이러한 환경에서 스프링이 어떻게 수만 개의 객체 요청을 효율적으로 처리하는지 그 핵심 원리인 싱글톤 컨테이너에 대해 알아본다.
1. 웹 애플리케이션과 싱글톤의 필요성
스프링 없는 순수한 DI 컨테이너인 AppConfig는 요청을 할 때마다 객체를 새로 생성한다. 만약 고객 트래픽이 초당 100회 발생한다면, 초당 100개의 서비스 객체가 생성되고 소멸되어야 한다. 이는 심각한 메모리 낭비를 초래한다.
이 문제를 해결하기 위해 해당 객체가 딱 1개만 생성되고, 이를 공유하도록 설계하는 것이 바로 싱글톤 패턴(Singleton Pattern)이다.
2. 스프링 컨테이너: 싱글톤 레지스트리
직접 자바 코드로 싱글톤 패턴을 구현하려면 코드 자체가 복잡해지고, DIP/OCP 원칙 위반, 테스트의 어려움 등 수많은 단점이 발생한다. 스프링 컨테이너는 이러한 싱글톤 패턴의 문제점을 해결하면서 객체 인스턴스를 싱글톤으로 관리한다.
- 싱글톤 레지스트리: 스프링 컨테이너는 별도의 싱글톤 패턴 코드를 추가하지 않아도 빈을 하나만 생성하여 관리한다.
- 장점: 지저분한 싱글톤 구현 코드 없이도 성능 최적화를 누릴 수 있으며, 프레임워크의 제약 없이 유연하게 객체를 설계할 수 있다.
3. 싱글톤 방식의 주의점: 무상태(Stateless) 설계
싱글톤 객체는 여러 클라이언트가 하나의 같은 인스턴스를 공유하기 때문에, 절대로 상태를 유지(Stateful)하게 설계하면 안 된다. 특정 클라이언트가 값을 변경할 수 있는 필드가 존재할 경우, 다른 클라이언트의 데이터가 오염되는 대형 장애가 발생할 수 있기 때문이다.
public class StatefulService {
private int price; // 상태를 유지하는 공유 필드
public void order(String name, int price) {
this.price = price; // 여기서 문제가 발생한다!
}
public int getPrice() {
return price;
}
}
해결 방안: 필드 대신 지역변수, 파라미터, ThreadLocal 등을 사용하여 공유되지 않는 방식으로 데이터를 처리해야 한다. 스프링 빈은 항상 무상태(Stateless)로 설계해야 함을 명심해야 한다.
4. @Configuration과 바이트코드 조작의 마법
AppConfig 코드를 보면 의구심이 생길 수 있다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
memberService를 만들 때 한 번, orderService를 만들 때 한 번, 총 두 번 memberRepository()가 호출되어 싱글톤이 깨지는 것처럼 보인다. 하지만 실제 테스트 결과는 모두 동일한 인스턴스를 참조한다.
그 비밀은 @Configuration에 있다. 스프링은 클래스의 바이트코드를 조작하는 CGLIB 라이브러리를 사용하여 우리가 만든 AppConfig를 상속받은 가짜 프록시 객체를 생성한다.
CGLIB 프록시의 동작 (가상 코드)
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
return 스프링 컨테이너에서 찾아서 반환;
} else {
기존 로직을 호출해서 생성 후 컨테이너에 등록;
return 반환;
}
}
이 기술 덕분에 자바 코드상으로는 여러 번 호출되는 것처럼 보여도, 실제로는 컨테이너에 등록된 기존 빈을 반환하여 싱글톤을 보장하게 된다. 만약 @Configuration 없이 @Bean만 사용한다면, 스프링 빈으로는 등록되지만 이와 같은 싱글톤 보장 기능은 사라지게 된다.
결론: 싱글톤은 스프링의 근간
스프링 컨테이너는 대규모 기업용 서비스를 지원하기 위해 탄생했으며, 그 핵심에는 효율적인 자원 관리를 위한 싱글톤 방식이 자리 잡고 있다. 개발자는 싱글톤의 공유 필드 문제를 항상 경계하고, 컨테이너가 제공하는 메커니즘을 신뢰하며 무상태 설계를 지향해야 한다.
'Spring > Core' 카테고리의 다른 글
| [Basic-7] 의존관계 자동 주입의 전략과 빈 충돌 해결 (0) | 2025.12.30 |
|---|---|
| [Basic-6] 컴포넌트 스캔과 자동 의존관계 주입 (0) | 2025.12.30 |
| [Basic-4] 스프링 컨테이너의 생성과 빈 관리 메커니즘 (0) | 2025.12.30 |
| [Basic-3] 관심사의 분리와 의존관계 주입(DI) (0) | 2025.12.30 |
| [Basic-2] 순수 자바 예제로 이해하는 DIP와 OCP 위반 (0) | 2025.12.30 |
