수동 DI(Dependency Injection)의 문제
객체를 필요할 때 직접 생성하면,
계속 새로운 인스턴스가 생성되어 메모리 낭비와 일관성 문제가 발생한다.
OrderService.java (문제 버전)
public class OrderService {
private PaymentProcessor paymentProcessor;
public OrderService() {
// 필요한 시점마다 새로 객체를 생성
this.paymentProcessor = new PaypalProcessor();
}
public void checkout(Order order) {
paymentProcessor.processPayment(order.getTotalAmount());
}
}
OrderService생성 시마다PaypalProcessor새 인스턴스 생성
Main.java
public class Main {
public static void main(String[] args) {
OrderService service1 = new OrderService();
OrderService service2 = new OrderService();
System.out.println(service1.paymentProcessor == service2.paymentProcessor); // false
}
}
service1과service2의paymentProcessor는 서로 다른 객체 → 불필요한 메모리 낭비!
문제를 해결하는 방법: 싱글톤 패턴
하나의 객체만 생성하고, 재사용하면 문제를 해결할 수 있다.
이를 위해 등장한 것이 전통적인 싱글톤 패턴
고전적인 싱글톤 구현 방법
Printer.java
public class Printer {
private static final Printer instance = new Printer();
// 외부에서 생성 못하게 private 생성자
private Printer() {}
// 유일한 인스턴스 제공
public static Printer getInstance() {
return instance;
}
public void print(String message) {
System.out.println("출력: " + message);
}
}
Printer객체는 애플리케이션 전체에서 단 하나만 존재합니다.
Main.java
public class Main {
public static void main(String[] args) {
Printer printer1 = Printer.getInstance();
Printer printer2 = Printer.getInstance();
printer1.print("Hello");
printer2.print("World");
System.out.println(printer1 == printer2); // true
}
}
printer1,printer2가 같은 인스턴스를 가리킵니다!
싱글톤 패턴의 한계
- 코드가 복잡해짐 (
private 생성자,static instance관리) - 테스트 어려움 (싱글톤 객체가 테스트 간 상태를 공유할 수 있음)
- DIP(의존 역전 원칙), OCP(개방-폐쇄 원칙) 위반 가능성
- 유연성이 떨어져 확장 및 변경이 힘듦
특히 규모가 커질수록 관리가 어려워진다.
스프링 컨테이너: 싱글톤 문제를 자동으로 해결!
스프링은 컨테이너가 객체를 싱글톤으로 자동 관리한다.
개발자가 직접 싱글톤 코드를 작성할 필요가 없다.
- 하나의 객체를 생성 → 모든 곳에 주입
- 객체 생명주기를 스프링이 관리
- 결합도 낮고 확장성 높은 아키텍처 구현 가능
스프링 싱글톤 Bean
Printer.java
import org.springframework.stereotype.Component;
@Component
public class Printer {
public void print(String message) {
System.out.println("출력: " + message);
}
}
@Component를 붙이면 스프링이 자동으로 Bean으로 등록하고, 싱글톤으로 관리합니다.
AppConfig.java
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}
Main.java (Spring Context 사용)
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Printer printer1 = context.getBean(Printer.class);
Printer printer2 = context.getBean(Printer.class);
printer1.print("Hello Spring");
printer2.print("Singleton Check");
System.out.println(printer1 == printer2); // true
}
}
printer1,printer2가 같은 객체 → 스프링이 자동으로 싱글톤 보장
만약 싱글톤이 아니게 하고 싶다면?
@Scope("prototype")을 사용하면 요청마다 새로운 객체를 생성합니다.
@Component
@Scope("prototype")
public class Printer {
...
}
기본은 싱글톤, 필요 시 프로토타입으로 설정 가능!
정리: 왜 스프링은 기본적으로 싱글톤을 사용할까?
| 이유 | 설명 |
|---|---|
| 성능 최적화 | 객체 생성을 최소화하여 메모리/CPU 절약 |
| 일관성 유지 | 동일 요청에 대해 항상 같은 객체 제공 |
| 관리 편의성 | 객체 생명주기 자동 관리, 개발자는 비즈니스 로직 집중 |
결론
- 수동 DI를 하면 객체가 계속 생성되어 비효율 발생
- 이를 막기 위해 고전적 싱글톤 패턴 등장
- 그러나 코드 복잡성, 테스트 어려움 등 부작용 존재
- 스프링은 컨테이너를 통해 자동으로 싱글톤을 보장하고, 관리 부담을 없앰
- 개발자는 "비즈니스 로직"에만 집중할 수 있게 됨
'SpringBoot' 카테고리의 다른 글
| 스프링 DI: 생존자 주입을 권장하는 이유 (0) | 2025.05.05 |
|---|---|
| 스프링 빈 자동 등록과 의존성 주입 (1) | 2025.05.01 |
| Spring Boot: 데이터 검증(Validation) (1) | 2024.11.22 |
| Spring Boot: 로깅(Logging) 구현하기 (1) | 2024.11.21 |
| Spring Boot: @ExceptionHandler와 @ControllerAdvice를 활용한 예외처리 (0) | 2024.11.20 |