백엔드 프레임워크/Spring Framework

[Spring Framework] 스프링 싱글톤: 실제 업무에서 마주하는 문제와 해결 방법

ioh'sDeveloper 2025. 1. 27. 15:56

실제 업무에서 마주하는 문제와 해결 방법

스프링을 사용하다 보면 개발자라면 누구나 한 번쯤 들어보는 개념이 바로 **싱글톤(Singleton)**입니다. 스프링 컨테이너(ApplicationContext)가 기본적으로 빈(Bean)을 싱글톤으로 관리한다는 사실은 많이 알려져 있지만, 이를 제대로 이해하지 못하면 예상치 못한 문제를 마주할 수 있습니다. 특히, 멀티스레드 환경에서의 동시성 문제, 빈 스코프의 잘못된 활용, 또는 상태 관리 실수 등은 실제 업무에서 흔히 발생하는 상황입니다.

이번 글에서는 스프링의 싱글톤이 왜 중요한지, 그리고 이를 실제 업무에서 어떻게 체감할 수 있는지 살펴보겠습니다. 또한, 흔히 발생하는 문제와 해결 방법을 함께 다뤄 안정적인 애플리케이션 설계를 위한 실질적인 팁을 공유합니다.

스프링 싱글톤의 작동 원리를 제대로 이해하고 나면, 효율적인 리소스 관리와 더불어 안정적인 시스템 설계에 한 걸음 더 다가갈 수 있을 것입니다. 😊


1. 싱글톤과 상태 관리 문제

실제 상황

  • 스프링에서 빈은 기본적으로 싱글톤으로 관리됩니다. 그런데 개발 중에 싱글톤 빈이 상태(state)를 가진 객체로 설계된다면, 멀티스레드 환경에서 동시성 문제가 발생할 수 있습니다.
  • 예시: 서비스 클래스에서 상태를 공유하는 변수를 잘못 설계한 경우:
    @Service
    public class MyService {
        private int count = 0;
    
        public void increment() {
            count++;
        }
    
        public int getCount() {
            return count;
        }
    }
    
    • 이 경우, 여러 사용자가 동시에 요청하면 count 값이 의도하지 않게 엉킬 수 있습니다.
    • 업무에서 영향: 예를 들어, 사용자가 장바구니에 물품을 추가하거나 주문 수량을 카운팅할 때 이상한 값이 나타날 수 있습니다.

교훈

  • 싱글톤 빈은 상태 없는(stateless) 설계를 지향해야 합니다.
  • 상태가 필요한 경우, 별도의 객체를 생성하거나 ThreadLocal 등을 사용하는 방식으로 해결해야 합니다.

2. 빈 스코프와 비효율적인 설계

실제 상황

  • 모든 빈을 싱글톤으로 관리하면 효율적일 것 같지만, 매 요청마다 다른 데이터를 반환해야 하는 경우에는 문제가 될 수 있습니다.
  • 예를 들어, 웹 애플리케이션에서 사용자마다 고유한 요청 데이터를 처리해야 하는 경우, 싱글톤 빈만 사용하면 같은 데이터를 반환하거나 이상 동작을 일으킬 수 있습니다.

실제 사례

  • REST API에서 요청별로 새로운 객체가 필요하지만, 실수로 빈 스코프를 싱글톤으로 설정한 경우:위 코드가 싱글톤 빈으로 등록되면, 모든 요청에서 마지막 요청의 userId 값이 반환됩니다. 사용자 간 데이터가 엉킬 수 있죠.
  • @Component public class UserRequest { private String userId; public void setUserId(String userId) { this.userId = userId; } public String getUserId() { return userId; } }

교훈

  • 요청마다 다른 객체를 생성하려면 스코프를 @Scope("prototype") 또는 **@Scope("request")**로 변경해야 합니다.
    @Component
    @Scope("request")
    public class UserRequest { ... }
    

3. 실제 업무에서의 활용

  • 공통 로직 관리: 싱글톤 빈을 활용하면 로깅, 인증, 외부 API 호출 등 공통 로직을 중앙에서 관리할 수 있습니다.
    • 예시: 모든 API 요청에서 사용되는 공통 인증 로직을 싱글톤으로 처리.
      @Component
      public class AuthService {
          public boolean isAuthenticated(String token) {
              // 인증 로직 처리
          }
      }
      
  • 효율적인 리소스 관리: 데이터베이스 커넥션 풀, 캐시, 스레드풀과 같은 리소스 관리도 싱글톤으로 처리합니다.
    • 업무 사례: Hibernate의 EntityManagerFactory를 싱글톤 빈으로 관리하여 효율적으로 데이터베이스 연결을 관리.

4. 업무에서 발생할 수 있는 문제와 해결 사례

문제 1: 동일한 빈이 다르게 동작

  • 상황: 같은 빈을 여러 곳에서 주입받았는데, 주입된 빈이 서로 다르게 동작함.
  • 원인: 프로토타입 스코프 빈이 싱글톤 빈 내부에서 주입된 경우.
  • 해결 방법: ObjectProvider 또는 @Lookup을 사용해 주입 시마다 새로운 프로토타입 빈을 반환.
    @Component
    public class SingletonBean {
        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;
    
        public void doSomething() {
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            // 매번 새로운 프로토타입 빈 사용
        }
    }
    

문제 2: 동시성 문제

  • 상황: 여러 사용자가 동시에 서비스 호출 시 데이터가 꼬임.
  • 해결 방법:
    • 상태를 가지지 않는 설계로 변경.
    • 동기화 블록 또는 ThreadLocal 사용:
      private ThreadLocal<Integer> threadLocalCount = ThreadLocal.withInitial(() -> 0);
      

5. 싱글톤을 이해하면 얻을 수 있는 이점

개발 효율성

  • 스프링 컨테이너가 객체 생성과 관리를 책임지므로, 개발자는 비즈니스 로직에만 집중할 수 있습니다.
  • 빈 주입(DI)을 활용하여 의존성을 관리할 수 있으므로, 단일 책임 원칙(SRP)을 유지하기 쉽습니다.

시스템 성능 최적화

  • 동일한 빈을 반복적으로 생성하지 않고, 하나의 객체를 재사용하므로 메모리와 CPU 리소스 절약.
  • 효율적인 리소스 관리로 대규모 트래픽을 처리할 수 있는 안정적인 애플리케이션 구현 가능.

문제 해결 능력 향상

  • 스프링의 싱글톤 구조와 빈 스코프를 정확히 이해하면, 발생하는 문제의 원인을 빠르게 파악하고 올바른 설계 방향을 선택할 수 있습니다.

6. 결론

스프링의 싱글톤 컨테이너는 효율적이고 안정적인 애플리케이션 개발을 가능하게 하는 핵심 기능입니다. 하지만, 이를 잘못 이해하거나 활용하면 동시성 문제, 데이터 엉킴, 비효율적인 리소스 사용 같은 문제가 발생할 수 있습니다.

핵심 요약:

  • 스프링의 싱글톤은 자바 싱글톤 패턴과 다르며, 컨테이너가 객체를 관리하는 방식입니다.
  • 업무에서는 상태 없는 설계와 빈 스코프를 적절히 활용하여 안정성을 유지해야 합니다.
  • 스프링의 싱글톤은 효율적인 리소스 관리와 코드 유지보수성 향상에 기여합니다.

여러분은 업무에서 싱글톤과 관련된 어떤 문제를 경험하셨나요? 댓글로 공유해 주세요! 😊