웹 애플리케이션은 여러 고객이 동시에 서비스를 요청하게 된다.
사실 당연하다.
오프라인 패스트푸드 매장 줄 서서 주문하듯이 한 명씩 주문하는 거 아니잖아?
여러 명(세션)이 동일한 서비스를 요청하면 프로그램에서는 무슨 일이 벌어질까?
해당 서비스에 관한 객체를 그때마다 생성해야 할까?
//20240209 V2.0 수정 - 결론을 상단에 배치 및 장표도 상단으로 당김, 결론 강조
싱글톤 방식은 그런 고민에서 출발했다.
클래스와 인스턴스를 딱 하나만 생성할 수 있도록 보장하는 디자인 패턴이다.
같은 서비스를 여러 사람(세션)이 호출할 때, 굳이 여러 개의 객체를 생성하지 않는다.
package hello.core.singleton;
import hello.core.AppConfig;
import hello.core.member.Member;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
//SingletonTest.java
public class SingletonTest {
@Test
@DisplayName("스프링없는 순수한 di컨테이너")
void pureContainer(){
AppConfig appConfig = new AppConfig();
//1.조회: 호출할때마다 객체 생성
MemberService memberService1 = appConfig.memberService();
//2.조회: 호출할때마다 객체생성
MemberService memberService2= appConfig.memberService();
//참조값이 다른것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
//memberService1 != memberService2
assertThat(memberService1).isNotSameAs(memberService2);
}
}
package hello.core;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//AppConfig.java
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){ //메소드명에서 역할이 드러나고,
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());//생성자 주입-> 20210926 리팩토링
}
@Bean
public MemberRepository memberRepository() { //구현하는 부분도 db설정 등에 따라 바뀔때 여기만 바꾸면 되고
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() { //할인정책은 여기서만 바꿔주면 되고
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
코드를 이렇게 짜놨으면 그렇다. AppConfig.java 는 요청할 때마다 객체를 새로 생성한다.
그렇다면, 트래픽이 초당 10000이면 초당 10000개의 객체를 생성하고 소멸해야 한다는 얘긴데
거 콤퓨타 양반, 이건 너무 메모리 낭비가 심한 거 아니오?
package hello.core.singleton;
//SingletonService.java
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance(){
return instance;
}
private SingletonService(){
}
}
//Singletontest.java 이어서
@Test
@DisplayName("싱글톤패턴을 적용한 객체사용")
void singletonServiceTest(){
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
System.out.println("singletonService1 = " + singletonService1);
System.out.println("singletonService1 = " + singletonService2);
assertThat(singletonService1).isSameAs(singletonService2);
}
SingletonService.java에서 SingletonService()를 호출할 때, private을 사용했다.
그리고 getInstance()를 통해서만 조회가 가능하게 했다.
이런 식으로 코딩하면 무조건 같은 singletonService를 가져올 수밖에 없다.
싱글톤은 이런 방식이라는 얘기를 참 길게도 했다.
그렇다면 쌩자바에서 싱글톤 방식을 채용하면 단점은 없을까? 당연 있다.
- 의존관계상 클라이언트가 구체 클래스에 의존하다 보니, DIP를 위반한다.
- 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
- 테스트, 내부 속성 변경. 초기화가 까다롭다.
- private 생성자로 자식 클래스를 만들기 어렵다: 결론적으로 유연성이 떨어진다.
//20240209 V2.0 수정 : 관련된 주제의 다른 참고글을 추가하였다.
여기서 DIP / OCP가 뭔지 잘 기억이 안 나면 SOLID 원칙을 복습하자
https://career-gogimandu.tistory.com/27
객체지향 설계에서 꼭 필요한 SOLID 5대원칙(SRP/OCP/LSP/ISP/DIP)
SRP : Single Responsibility Principle, 단일책임 원칙 OCP : Open Closed Principle, 개방-폐쇄 원칙 LSP : Liskov Subtitution Principle, 리스코프 치환원칙 ISP : Interface Segregation Principle, 인터페이스 분리 원칙 DIP : Dependency
career-gogimandu.tistory.com
그런데, 지금 공부하는 건 쌩자바 방식이 아닌, 스프링 '프레임워크'다.
이런 단점을 해결해 주라고 있는 게 프레임워크 아니었나?
스프링 컨테이너는 싱글톤 패턴을 따로 적용하지 않아도,
싱글톤 컨테이너의 역할을 할 수 있다는 장점을 갖는다.
저런 지저분한 쌩노가다를 굳이 안 해도 된다는 얘기다.
@Test
@DisplayName("스프링컨테이너와 싱글톤")
void springContainer(){
//AppConfig appConfig = new AppConfig();
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);
}
}
//SingletonTest.java 이어서
결과는 memberService1 = memberService2
대신, 이게 가능하려면 주의해야 할 게 있다.
여러 클라이언트가 같은 하나의 객체 인스턴스를 공유하려면,
싱글톤 객체는 상태를 가지면 안 된다.
- 특정 클라이언트에 의존적이어서도 안 되고
- 특정 클라이언트가 값을 변경해서도 안 되고
- 가급적 읽기만 가능하면 더욱 좋고
- 자바에서 공유되지 않는 지역변수/파라미터 등을 사용하는 편이 유리하다.
//작성에 도움받은 자료 [스프링 핵심원리] - 인프런 강의자료
//V2.0 20240209 수정 - 개행 일부 수정 하고 맞춤법 검사를 하였습니다. 생각보다 편하게 써 내려가다 보면 띄어쓰기가 표준에 맞지 않는 경우가 있어요.
V1.0 글을 풀스크린 캡쳐로 아카이브 해놨는데, 궁금하신 분은 접은 글을 펼쳐 더보기 ▽
'study > Spring' 카테고리의 다른 글
객체지향 설계에서 꼭 필요한 SOLID 5대원칙(SRP/OCP/LSP/ISP/DIP) (0) | 2021.09.22 |
---|---|
JPA란 무엇인가? (0) | 2021.09.11 |
MVC 패턴 구조에 대하여 알아보자 (0) | 2021.08.21 |
스프링 빌드가 안 돼요! (0) | 2021.08.15 |
댓글