1. Spring Bean Scope란?

싱글톤 패턴이란?

싱글톤(Singleton) 패턴은 클래스의 인스턴스를 하나만 생성하여 전역적으로 공유하는 디자인 패턴입니다. 생성자를 private으로 막고 정적 메서드를 통해 유일한 인스턴스를 반환하는 방식으로 구현됩니다.

  • 사용 목적: 메모리 절약, 인스턴스 재사용, 설정이나 캐시 등의 전역 상태 공유
  • 적용 사례: 설정 클래스, 로깅 유틸, 스프링의 서비스 클래스

Spring은 이러한 싱글톤 패턴을 프레임워크 차원에서 기본 지원합니다. 대부분의 Bean은 특별한 설정이 없으면 singleton 스코프로 관리되며, 이는 내부적으로 싱글톤 패턴처럼 동작합니다.

하지만 모든 상황에 싱글톤이 적합한 것은 아닙니다. 요청(Request)마다 새로운 객체가 필요하거나, 사용자(Session)마다 상태를 분리해야 하는 경우에는 다른 스코프가 필요합니다.

따라서 Spring에서는 다양한 Bean Scope 설정을 통해 개발자가 목적에 따라 인스턴스 생명주기를 조절할 수 있도록 돕습니다.

 

싱글톤 Bean의 동작 방식과 주의점

Spring에서 싱글톤 스코프의 Bean은 하나의 인스턴스를 여러 요청이 공유하게 됩니다. 하지만 이는 요청이 순차적으로 처리된다는 뜻은 아닙니다. Spring Boot의 내장 웹 서버는 멀티스레드 기반으로 작동하기 때문에 동시에 들어온 요청들도 모두 같은 싱글톤 인스턴스를 동시에 참조하게 됩니다. 즉, 10명의 사용자가 같은 API를 동시에 호출하더라도 각각의 요청은 다른 스레드에서 처리되며, 같은 Bean 인스턴스를 공유합니다.

이로 인해 싱글톤 Bean 내부에서 상태를 가지는 필드가 있다면 동시성 문제가 발생할 수 있습니다. 따라서 싱글톤은 반드시 stateless하게 설계하는 것이 원칙입니다.

문제 사례

@Service
public class BadService {
    private String username; // 상태를 가지는 필드

    public void setUser(String name) {
        this.username = name;
    }

    public String getUser() {
        return this.username;
    }
}

위 코드는 여러 요청이 동시에 setUser()getUser()를 호출하면 값이 꼬일 수 있습니다.

이 문제는 public으로 선언되었기 때문이 아니라, Bean이 싱글톤이면서 상태(state)를 내부 필드로 보존하고 있기 때문에 생기는 문제입니다. 즉, 필드가 private여도 상태가 공유되면 동일하게 위험합니다.

하지만, 실제로 MVC 아키텍처에서는 DTO를 이용해 요청 값을 주고받는 구조가 일반적이며, 이 DTO들은 매 요청마다 생성되므로 상태 공유 문제가 발생하지 않습니다. 또한 서비스 계층은 대부분 stateless하게 구성되며, 필요한 데이터는 DB에서 조회하거나 파라미터로 전달받기 때문에 싱글톤의 상태 공유 문제와는 거리가 멉니다.


2. 주요 Scope 종류

Scope 이름 생성 범위 사용 예시 및 설명
singleton ApplicationContext 당 1개 생성 기본값. 서비스, DAO 등 재사용 가능한 컴포넌트
prototype 요청할 때마다 새로 생성됨 상태가 독립적인 객체 (ex: 동적 파라미터 기반 생성)
request HTTP 요청마다 1개 생성 요청 단위의 로깅/검증 객체, DTO 처리 (Web 환경)
session HTTP 세션마다 1개 생성 로그인 사용자 정보 저장, 세션 기반 상태 유지
application ServletContext 당 1개 전역 설정이나 공유 캐시

request, session, application 스코프는 Spring Web (Servlet) 환경이 구성된 경우에만 사용 가능합니다. 즉, Spring Boot에서도 spring-boot-starter-web 의존성을 포함한 웹 프로젝트에서는 정상적으로 동작하지만, CLI 또는 테스트 환경에서는 스코프 등록 오류가 발생할 수 있습니다.


3. 실전 예시별 적용 스코프 비교

실전 시나리오 권장 Scope 이유
API 서비스 로직 (UserService 등) singleton 상태 없음, 재사용성 중요
매 요청마다 검사하는 Validator request 요청 간 상태 분리 필요
로그인한 유저 정보 저장 session 세션 단위로 유지되어야 함
요청마다 DTO 새로 만들기 prototype 파라미터 기반 객체 필요

4. request + session 복합 사용 예시

로그인 사용자 정보를 저장하고, 요청마다 그 값을 검증하는 시나리오를 예로 들어봅니다.

SessionUser.java

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionUser {
    private String username;
    private LocalDateTime loginTime = LocalDateTime.now();

    // getter/setter
}

RequestLogger.java

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestLogger {
    public RequestLogger() {
        System.out.println("[RequestLogger] New instance created");
    }
}

Controller 예시

@RestController
public class UserController {

    @Autowired
    private SessionUser sessionUser;

    @Autowired
    private RequestLogger requestLogger;

    @GetMapping("/user")
    public String getUser() {
        System.out.println("RequestLogger: " + requestLogger.hashCode());
        return "Hello, " + sessionUser.getUsername();
    }
}

5. 테스트 시 주의사항

  • request, session 스코프는 Spring Boot Test 환경에서 WebApplicationContext로 테스트해야 정상 동작합니다.
  • @WebMvcTest 또는 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 등을 사용해야 함.
  • 일반 JUnit 테스트에서는 IllegalStateException: No Scope registered for scope 'request' 발생 가능.

6. 요약

Scope 언제 사용하나?
singleton 대부분의 경우 기본 선택
prototype 독립적인 상태를 가진 객체가 필요한 경우
request 요청 단위 분리 처리 시 (ex. API 로깅, 유효성 검증)
session 사용자별로 로그인 상태나 설정을 유지해야 할 때
 

1. 디자인 패턴이란?

디자인 패턴은 소프트웨어 설계 시 반복적으로 등장하는 문제를 해결하기 위한 일종의 '템플릿'이다. Java Spring에서는 프레임워크 특성상 구조적인 패턴(예: MVC)이나 생성/행위 패턴을 많이 활용한다.

실제로 Spring 기반 프로젝트를 개발하다 보면, 작은 규모의 서비스라도 다양한 디자인 패턴이 유기적으로 사용되는 것을 볼 수 있습니다. 각 컴포넌트가 자신의 책임에 집중하고, 변화에 유연하게 대응할 수 있도록 설계하는 과정에서 자연스럽게 전략(Strategy), 프록시(Proxy), 빌더(Builder) 등 다수의 패턴이 녹아들게 됩니다.

2. 시기별 디자인 패턴 사용 경향

시기 특징 주로 사용된 패턴
Spring Framework 2.x XML 기반 설정, DI/IoC 초창기 Singleton, Factory, Proxy, Template Method
Spring 3.x ~ 4.x Java Config 전환, AOP 정착 Strategy, Decorator, Observer, Command
Spring Boot Auto-configuration, Starter 기반 설정 최소화 Builder, Adapter, Chain of Responsibility, Specification

3. 주요 디자인 패턴 정리 및 실무 예제

 

Singleton 패턴

  • 정의: 인스턴스를 하나만 생성해 공유
  • Spring 적용: 기본 Bean scope가 Singleton
  • 대표 클래스: Spring의 모든 @Component, @Service, @Repository
  • UML (Text 기반):
+----------------+
|   Singleton    |
+----------------+
| - instance     |
+----------------+
| + getInstance()|
+----------------+
  • 실무 예제:
@Service
public class NotificationService {
    public void send(String message) {
        System.out.println("Sending message: " + message);
    }
}

Factory 패턴

  • 정의: 객체 생성 로직을 캡슐화
  • Spring 적용: BeanFactory, FactoryBean
  • 대표 클래스: LocalSessionFactoryBean, SqlSessionFactoryBean
  • UML (Text 기반):
      +-------------+
      |   Product   |
      +-------------+
         ^       ^
         |       |
+---------------------+
|ConcreteProductA/B   |
+---------------------+
         ^
         |
+---------------------+
|   ProductFactory    |
| +createProduct()    |
+---------------------+
  • 실무 예제:
public class AnimalFactory {
    public static Animal createAnimal(String type) {
        return switch (type) {
            case "dog" -> new Dog();
            case "cat" -> new Cat();
            default -> throw new IllegalArgumentException("Unknown type");
        };
    }
}

Proxy 패턴

  • 정의: 대리 객체를 통해 원래 객체 제어
  • Spring 적용: AOP (Aspect Oriented Programming)
  • 대표 클래스: ProxyFactoryBean, @Transactional
  • UML (Text 기반):
     +---------+
     | Subject |
     +---------+
        ^   ^
        |   |
+--------------+  +--------+
| RealSubject  |  | Proxy  |
+--------------+  +--------+
                     |
                     v
              +-------------+
              | RealSubject |
              +-------------+
  • 실무 예제:
@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
        System.out.println("메서드 실행 전 로그 출력");
    }
}

Template Method 패턴

  • 정의: 알고리즘 골격을 정의하고, 일부 단계는 서브클래스에서 구현
  • Spring 적용: JdbcTemplate, RestTemplate
  • 대표 클래스: JdbcTemplate, AbstractController
  • UML (Text 기반):
+------------------+
|  AbstractClass   |
+------------------+
| +templateMethod()|
| +primitiveOp1()  |
| +primitiveOp2()  |
+------------------+
          ^
          |
+------------------+
|  ConcreteClass   |
+------------------+
| +primitiveOp1()  |
| +primitiveOp2()  |
+------------------+
  • 실무 예제:
public abstract class AbstractTask {
    public void execute() {
        start();
        process();
        end();
    }
    protected abstract void process();
    private void start() { System.out.println("Start"); }
    private void end() { System.out.println("End"); }
}

Strategy 패턴

  • 정의: 알고리즘을 클래스로 분리해 캡슐화
  • Spring 적용: AuthenticationProvider, ViewResolver
  • 대표 클래스: HandlerMethodArgumentResolver
  • UML (Text 기반):
     +------------+
     |  Strategy  |
     +------------+
        ^     ^
        |     |
+-------------------+
|ConcreteStrategies |
+-------------------+
        ^
        |
   +-----------+
   |  Context   |
   +-----------+
   | -strategy |
   | +execute()|
   +-----------+
  • 실무 예제:
public interface PaymentStrategy {
    void pay(int amount);
}

public class CreditCardPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using credit card.");
    }
}


Builder 패턴

  • 정의: 복잡한 객체 생성을 단계별로 처리
  • Spring 적용: ResponseEntity.builder(), Lombok @Builder
  • 대표 클래스: ResponseEntity, User.builder()
  • UML (Text 기반):
+-----------+       +-----------+
|  Director | ----> |  Builder  |
+-----------+       +-----------+
                         ^
                         |
                +------------------+
                | ConcreteBuilder  |
                +------------------+
                         |
                         v
                    +---------+
                    | Product |
                    +---------+
  • 실무 예제:
@Builder
public class User {
    private String name;
    private int age;
}

Adapter 패턴

  • 정의: 인터페이스 호환을 위한 중간 계층
  • Spring 적용: HandlerAdapter, MessageConverter
  • 대표 클래스: HttpMessageConverter, HandlerAdapter
  • UML (Text 기반):
     +--------+
     | Target |
     +--------+
         ^
         |
     +--------+
     | Adapter|
     +--------+
         |
         v
     +--------+
     | Adaptee|
     +--------+
  • 실무 예제:
public interface MediaPlayer {
    void play(String fileName);
}

public class AudioPlayer implements MediaPlayer {
    public void play(String fileName) {
        System.out.println("Playing " + fileName);
    }
}

Specification 패턴

  • 정의: 조건을 조합해 쿼리 작성
  • Spring 적용: JpaSpecificationExecutor
  • 대표 클래스: Specification<T>
  • UML (Text 기반):
      +------------------+
      |  Specification   |
      +------------------+
      | +isSatisfiedBy() |
      | +and(), or(), not()|
      +------------------+
               ^
               |
   +----------------------------+
   |   ConcreteSpecification   |
   +----------------------------+
  • 실무 예제:
Specification<User> hasName(String name) = (root, query, cb) -> cb.equal(root.get("name"), name);

4. 목적별 디자인 패턴 추천 가이드

Spring 프로젝트에서 상황에 따라 어떤 디자인 패턴을 적용하면 좋을지 정리하면 다음과 같습니다:

목적/상황 추천 패턴 설명
JPA 동적 쿼리 처리 Specification 조건 조합 및 유연한 쿼리 생성을 위해 적합
다양한 외부 시스템 연계 Adapter, Template Method 연계 대상별로 추상화하여 유연하게 연동 가능
복잡한 객체 생성 Builder DTO나 Response 객체를 생성할 때 단계적 설정이 가능
비즈니스 로직 알고리즘 분리 Strategy 정책, 계산 방식, 인증처리 등 알고리즘 선택에 유리
횡단 관심사 처리 Proxy 트랜잭션, 로깅, 인증/인가 등 AOP 기반 처리
템플릿 제공 후 확장 유도 Template Method API 호출, DB 연동 등 공통 로직 제공 후 커스터마이징 허용
서로 다른 구현 통합 필요 Adapter 외부 API, 레거시 시스템 등을 일관된 인터페이스로 변환

이 표를 바탕으로 실무 상황에 맞는 패턴을 선택하면 아키텍처의 유연성과 유지보수성이 크게 향상됩니다

'Back-End > Java & Spring' 카테고리의 다른 글

Factory 패턴 개념  (0) 2025.05.11
싱글톤 패턴 개념& Spring Bean Scope  (0) 2025.05.11
Java GC 로그 해석과 튜닝 전략 정리  (0) 2025.05.10
Java GC 알고리즘 정리  (0) 2025.05.10
CORS 오류 해결하기(Spring)  (0) 2025.03.02

Java 애플리케이션에서 성능 문제의 많은 부분은 GC(Garbage Collection) 동작과 밀접한 관련이 있다. GC 로그를 통해 메모리 사용 상태를 파악하고, 튜닝 전략을 세워 시스템 안정성과 응답성을 높일 수 있다.


1. GC 로그 수집 설정

JVM 실행 시 다음 옵션을 통해 GC 로그를 수집할 수 있다:

-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/var/log/gc.log

JDK 9 이상에서는 Unified Logging을 사용:

-Xlog:gc*:file=gc.log:tags,uptime,time,level

2. GC 로그 예시와 해석 (G1GC 기준)

2024-05-10T12:00:01.123+0000: 2.345: [GC pause (G1 Evacuation Pause) (young), 0.0456789 secs]
   [Parallel Time: 42.3 ms, GC Workers: 4]
   [Eden: 256M(256M)->0B(128M) Survivors: 32M->64M Heap: 1024M(2048M)->800M(2048M)]

주요 해석 포인트:

  • GC 종류: Young GC (G1 Evacuation Pause)
  • Pause Time: 0.045초 → GC로 인한 멈춤 시간
  • Heap Before/After: 1024MB → 800MB (224MB 회수)

경고 신호:

  • Full GC, to-space exhausted, Allocation Failure: 메모리 부족 가능성
  • Pause Time이 1초 이상이면 사용자 체감 가능 → 튜닝 필요

3. GC 튜닝 전략

튜닝은 애플리케이션 특성과 사용 환경에 따라 다르며, 아래는 일반적인 방향성이다.

A. Heap 사이즈 조정

-Xms2g -Xmx4g  # 초기값과 최대값 설정
  • 너무 작으면 GC 반복 발생
  • 너무 크면 Full GC 시 오히려 문제

B. G1GC 튜닝 옵션 예시

-XX:+UseG1GC
-XX:MaxGCPauseMillis=200         # 최대 멈춤 시간 설정 (ms)
-XX:InitiatingHeapOccupancyPercent=45  # Old GC 발생 기준

C. Throughput/Latency 목표에 따른 선택

목적 전략
최대 처리량 ParallelGC, 큰 Heap, 낮은 Pause 예외 허용
짧은 응답 시간 G1GC, ZGC, Heap 최소화, MaxGCPauseMillis 조절

4. 분석 도구 추천

  • GCViewer: 시각화 기반 GC 로그 해석
  • GCEasy.io: 웹 기반 분석 자동화 도구
  • JClarity Censum: 상용 도구, 히트맵 분석 제공

+ Recent posts