728x90
반응형
SMALL
스프링이 없는 순수한 DI 컨테이너
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainter() {
AppConfig appConfig = new AppConfig();
// 호출할 때마다 객체를 생성
MemberService memberService1 = appConfig.memberService();
MemberService memberService2 = appConfig.memberService();
// 참조값이 다르다. JVM 메모리에 새로운 객체가 계속 생성되어 저장된다.
assertThat(memberService1).isNotSameAs(memberService2);
}
호출이 올 때마다 객체를 새로 생성하고, 이후 소멸하기에 메모리 낭비가 심하다.
싱글톤 패턴
- 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.
- 그래서 객체 인스턴스를 2개 이상 생성하지 못하도록 막아야 한다.
- private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야 한다.
public class SingletonService {
// static 영역에 객체를 딱 1개만 생성해둔다.
private static final SingletonService instance = new SingletonService();
// public으로 열어서 객체 인스턴스가 필요하면 getInstance()를 통해서만 조회하도록 허용한다.
public static SingletonService getInstance() {
return instance;
}
// private으로 생성자를 선언하여 new 키워드를 통한 객체 생성을 막는다.
private SingletonService() {
}
public void logic() {
System.out.println("싱글톤 객체 로직 호출");
}
}
@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonServiceTest() {
// SingletonService() has private access ~
// new SingletonService();
// getInstance()를 통해 static 영역에 선언된 객체를 사용한다.
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
// 참조값이 같다.
assertThat(singletonService1).isSameAs(singletonService2);
}
호출이 올 때마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 사용한다.
싱글톤 컨테이너
- 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
- 스프링 컨테이너 자체가 싱글톤 컨테이너 역할을 한다. (= 싱글톤 레지스트)
- 싱글톤 패턴을 위한 지저분한 코드가 필요하지 않다.
- DIP, OCP, 테스트, private 생성자로부터 자유롭게 싱글톤을 사용할 수 있다.
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
assertThat(memberService1).isSameAs(memberService2);
}
스프링의 기본 빈 등록 방식은 싱글톤이지만, 다른 방식도 지원한다.
singleton, protorype, request, session, global session.... => bean scope 참고
싱글톤 방식의 주의점
- 객체 인스턴스를 하나만 생성해서 공유하는 방식이기에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안 된다.
- 무상태(stateless)로 설계해야 한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안 된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안 된다.
- 가급적 읽기만 가능해야 한다.
- 필드 대신에 자바에서 공유되지 않는 지역 변수, 파라미터, ThreadLocal 등을 사용해야 한다.
public class StatefulService {
private int price; // 상태를 유지하는 필드
public void order(String name, int price) {
this.price = price; // 여기가 문제!
}
public int getPrice() {
return price;
}
}
@Test
void statefulServiceSingleton() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
// Thread A : A사용자 10000원 주문
statefulService1.order("userA", 10000);
// Thread B : B사용자 20000원 주문
statefulService1.order("userB", 20000);
// Thread A : A사용자 주문 금액 조회
int price = statefulService1.getPrice();
// A사용자의 주문 금액 10000이 아닌 B사용자의 주문 금액인 20000이 나온다.
assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
static class TestConfig {
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
공유 필드는 항상 주의, 스프링 빈은 항상 무상태(stateless)로 설계해야 한다.
@Configuration
- 스프링 컨테이너는 싱글톤 레지스트리다. 따라서 스프링 빈이 싱글톤이 되도록 보장해주어야 한다.
- 스프링은 CGLIB이라는 바이트코드 조작 라이브러리를 통해서 임의의 클래스를 만들고, 그 클래스를 빈으로 등록한다.
- @Bean 어노테이션만 있어도 스프링 빈으로 등록되지만, 싱글톤을 보장하지는 않는다.
- 그냥 @Configuration 쓰자.
728x90
반응형
LIST
'Study > [Inflearn] 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
[Spring - Inflearn] Spring Bean Scope - 스프링 빈 스코프 (0) | 2021.02.07 |
---|---|
[Spring - Inflearn] Component & ComponentScan & 의존관계 주입 (0) | 2021.01.19 |
[Spring - Inflearn] Spring Container & Bean (0) | 2021.01.10 |
[Spring - Inflearn] IoC & DI, IoC Container & DI Container (0) | 2021.01.10 |
[Spring - Inflearn] Spring & Springboot & 객체 지향 설계 (0) | 2021.01.03 |