웹 애플리케이션과 싱글톤
- 한번에 수많은 사람들이 접속함
- 이때 지금까지의 순수한 자바만의 AppConfig 는 요청마다 객체를 새로 생성
- 이러면 메모리 낭비가 심해진다.
- 해결방안으로 해당 객체가 딱 1개만 생성되어 공유하는 싱글톤 패턴이 등장

싱글톤 패턴
- 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
구현방법
- static 영역에 객체 instance를 미리 하나 생성
- 해당 인스턴스가 필요하면 오직 getter를 통해서만 조회가능. 호출하면 static영역이기에 같은 인스턴스만이 반환됨
- 생성자를 private로 막아 외부에서 new로 해당 인스턴스를 생성하는것을 막음
public class SingletonService {
//1. static 영역에 객체를 딱 1개만 생성해둔다.
private static final SingletonService instance = new SingletonService();
//2. public으로 열어서 객체 인스터스가 필요하면 이 static 메서드를 통해서만 조회하도록
허용한다.
public static SingletonService getInstance() {
return instance;
}
//3. 생성자를 private으로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
private SingletonService() {
}
public void logic() {
System.out.println("싱글톤 객체 로직 호출");
}
}
싱글톤 패턴 문제점
- 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
- 의존관계상 클라이언트가 구체 클래스에 의존한다. -> DIP를 위반한다.
- 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
- 테스트하기 어렵다.
- 내부 속성을 변경하거나 초기화 하기 어렵다.
- private 생성자로 자식 클래스를 만들기 어렵다.
- 결론적으로 유연성이 떨어진다.
싱글톤 컨테이너
- 위의 싱글톤 패턴의 모든 단점을 보완해주는 스프링의 싱글톤 컨테이너이다.
- 지금까지 사용한 스프링빈이 싱글톤으로 관리되는 빈이다.
스프링 컨테이너 장점
- 스프링 컨테이너는 따로 싱글톤 패턴을 적용하지 않아도, 객체인스턴스(스프링빈)을 싱글톤으로 관리
- 스프링 컨테이너의 이런 기능 덕분에 싱글톤의 모든 단점을 보완하고, 객체를 싱글톤으로 유지할수 있다
- 싱글톤 패턴을 위한 지저분한 코드 필요 없음
- DIP, OCR, 테스트, private 생성자로부터 자유롭게 싱글톤 사용
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
ApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);
//1. 조회: 호출할 때 마다 같은 객체를 반환
MemberService memberService1 = ac.getBean("memberService",
MemberService.class);
//2. 조회: 호출할 때 마다 같은 객체를 반환
MemberService memberService2 = ac.getBean("memberService",
MemberService.class);
//참조값이 같은 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
//memberService1 == memberService2
assertThat(memberService1).isSameAs(memberService2);
}
싱글톤 방식의 주의점
- 무상태(stateless)로 설계해야한다.
- 싱글톤 방식은 여러 클라이언트가 공유해서 사용하므로 공유되는 필드가 있으면 안된다.
- 특정클라이언트가 값을 변경할수 있으면 안된다.!!!!! 가급적 읽기만 가능해야함!
public class StatefulService {
private int price; //상태를 유지하는 필드
public void order(String name, int price) {
System.out.println("name = " + name + " price = " + price);
this.price = price; //여기가 문제!
}
public int getPrice() {
return price;
}
}
- 클라이언트1 이 price1 을 넣고, 클라이언트2가 price2를 넣은후 클라이언트1 이 price를 요청하면 price2가 나오게된다. 상태가 유지(공유필수(price)가 사용)되고 있기 때문!
- 정말 조심하기
@Configuration과 싱글톤
- AppConfig가 다음과 같은데 스프링 컨테이너에 스프링 빈을 등록한다면 자바코드에 따르면 new MemoryRepository가 3번 호출되니까 싱글톤이 깨지는거 아닌가?
- memberRepository를 스프링 컨테이너에 등록할때
- memberService에서 호출할때
- orderService에서 호출할때
@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();
}
}
@Configuration과 바이트코드 조작의 마법
- 위의 문제가 일어나는 것은 맞지만 @Configuration이 해결해준다.
- AppConfig를 스프링 빈으로 등록하고 조회해보자
AppConfig bean = ac.getBean(AppConfig.class);
- 다음과 같은 클래스 정보가 출력된다.
bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$bd479d70
- 순수한 클래스와 다르게 CGLIB가 붙어있는것을 볼수 있는데 이것은 바이트코드 조작라이브러리를 이용했다는 것으로 AppConfig클래스를 상속받은 임의의 다른 클래스를 만들고 다른 클래스를 스프링 빈으로 등록한것이다.
- 그 임의의 다른 클래스가 바로 싱글톤을 보장해준다.
AppConfig@CGLIB 예상코드
- @Bean이 붙은 메서드마다 스프링 컨테이너에 존재한다면 컨테이너의 싱글톤을 보장받아 존재하는 빈을 반환하고, 빈이 없으면 컨테이너에 등록하고, 반환하는 코드가 스프링에 의해 동적으로 만들어진다.
- 덕분에 싱글톤 보장가능
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
return 스프링 컨테이너에서 찾아서 반환;
}
else { //스프링 컨테이너에 없으면 기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환
}
}
참고강의
728x90
'Spring' 카테고리의 다른 글
JPA Auditing기능이란? (0) | 2022.12.29 |
---|---|
스프링 핵심 원리[기본편] - 의존관계 자동 주입 (0) | 2022.12.28 |
스프링 핵심 원리[기본편] - 컴포넌트 스캔 (0) | 2022.12.27 |
스프링 핵심 원리[기본편] - 스프링 컨테이너와 스프링 빈 (0) | 2022.12.27 |
스프링 핵심 원리[기본편] - 예제만들기 , 객체 지향 원리 적용 (0) | 2022.12.27 |