인프런 강의 中
김영한 강사님의 '스프링 핵심 원리' 내용을 정리했습니다.
DIP, OCP 법칙을 준수하며 Dependency Injection을 사용해 개발을 진행해보고, 스프링 빈을 사용해 스프링 컨테이너에서 인스턴스들을 관리하는 코드를 작성해보았다. 그리고 빈이 어떻게 설정되는지까지 배워보았다. Section4까지의 강의를 학습해보면 딱히 왜 스프링 컨테이너를 사용하는지 이유를 찾을 수 없었다. 왜냐하면 순수 자바 코드로 작성해도 같은 결과를 가져왔다고 생각했기 때문이다. 하지만! 사실 스프링 컨테이너를 사용했을 때 엄청난 차이가 있었다. 바로 Singleton 패턴을 자동으로 적용해준다는 것이다. 물론 자바 코드로도 Singleton 패턴을 적용할 수 있다. 하지만 자동! 으로 해준다는건 그만큼 사용성이 좋다는 것이다. 그리고 자바 코드로 작성시 발생하는 Singleton의 단점들을 스프링 컨테이너에서 잡아주기도 한다. Section5부터 김영한 강사님의 강의를 토대로 Singleton의 효과와 스프링 컨테이너를 통해 Singleton 패턴을 사용시 이점에 대해 학습해보자!
목차
- Singleton을 사용하지 않고 개발을 하게 된다면?
- Singleton 개념
- 순수 자바 코드에서의 Singleton
- 스프링 컨테이너에서의 Singleton
- Singleton의 주의점
1. Singleton을 사용하지 않고 개발을 하게 된다면?
우리는 일반적으로 많은 사용자들이 사용할 수 있는 어플리케이션을 개발한다. 그렇다면 고객의 요청이 동시에 발생할 경우가 빈번하다. 이 때, 고객 마다 서비스 객체를 생성한다고 생각하면 어마무시한 객체 생성이 발생할 수 있다. 메이플스토리라는 게임이 동시 접속자수 40만명을 돌파했었다는 기사를 접했던 적이 있다. 메이플스토리라는 게임이 접속자수 마다 객체가 생성되도록 코드를 작성했다면 1초에 40만개의 서비스 객체가 생성되었을 것이다.. 물론 필요한 객체라면 생성되어야 할 수 있겠지만 NPC와 같이 모두가 사용하는 서비스 객체를 방대한 양으로 생성하게 된다면 생각만해도 메모리 낭비가 엄청나다고 느껴진다. 이렇게 사용자 마다 객체를 생성하는 형태를 김영한 강사님께서 이미지로 만들어주셨다.
위 이미지는 우리가 작성했던 AppConfig 클래스를 기반으로 객체가 생성되는 모습을 보여준다. 그럼 실제로 위 그림처럼 사용자가 memberService를 호출할 때마다 인스턴스가 생성되는지 순수 자바 코드로 테스트 해보자.
appConfig에 담겨있는 memberService를 두 번 호출하고 호출된 인스턴스를 확인해보니 참조된 값이 다른 것을 확인할 수 있다. 즉, 호출할 때마다 인스턴스가 생긴 것이다. 이렇게 Singleton 방식을 사용하지 않고 개발을 하게 된다면 불필요한 객체 생성을 하게 된다는 것을 확인했다. 그렇다면 Singleton 방식을 사용하면 어떻게 될 것인가?
2. Singleton 개념 및 코드
Singleton은 Single이라는 이름처럼 '하나의' 인스턴스만 생성해서 사용하는 방식이다.
위에서 필자는 불필요한 객체 생성을 한다고 찡찡거렸다. 이러한 필자의 불만을 Singleton이 해결해줄 수 있다. 다시 메이플스토리를 예로 들어 보자. 40만명이 메이플스토리 NPC 객체에 접근할 때, Singleton 방식을 사용하면 단 하나의 NPC 객체만 생성하여 40만명에게 하나의 NPC를 공유할 수 있다!! 어짜피 모든 사람들에게 같은 말과 미션을 주는 NPC를 40만개 만들지않고 1개만 만들어서 아주 효율적으로 사용할 수 있도록 해주는 멋진 녀석이 바로 Singleton이다.
3. 순수 자바 코드에서의 Singleton
(우리는 자바 코드로 Singleton을 작성해봄으로써 스프링 컨테이너가 얼마나 번거로운 작업을 자동으로 해주는지 몸소 체험해볼 것이다.)
이러한 Singleton 코드를 작성시 주의해야할 점이 있다. 바로 사람들이 인스턴스를 2개 이상 생성하지 못하도록 막아햐 하는 것이다. 그래서 순수 자바 코드로 Singleton 코드를 작성할 때 private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막아준다.
Singleton 클래스는 자기 자신을 내부적으로 static형태(static 형태면 인스턴스 하나만 생성할 수 있음)로 가지고 있으며, getInstance 메소드를 사용해 인스턴스를 호출할 수 있다. 그리고 위에서 말했듯 외부에서 new로 인스턴스 생성을 할 수 없도록 private를 사용해 생성자를 만든다.
참고: 싱글톤 팬턴을 구현하는 방법은 여러가지가 있다. 여기서는 객체를 미리 생성해두는 가장 단순하고 안전한 방법을 선택했다.
자, 이제 위에서 작성한 코드를 또 테스트해봄으로써 정말 여러 번 호출해도 하나의 인스턴스만 생성하는지 확인해보자.
테스트 결과를 통해서 하나의 인스턴스만 사용하는 모습을 확인할 수 있다.
이제 필자가 처음에 말했던 문제를 해결했다아아하지만, Singleton 방식은 또 다음과 같은 문제점을 가지고 있다.
- 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
- 의존관계상 클라이언트가 구체 클래스에 의존한다. -> DIP를 위반한다.
- 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
- 테스트하기 어렵다.
- 내부 속성을 변경하거나 초기화 하기 어렵다.
- private 생성자로 자식 클래스를 만들기 어렵다.
- 결론적으로 유연성이 떨어진다.
- 안티패턴으로 불리기도 한다. => 인스턴스 1개를 보장하지 못한다.(Tremendous issue)
What the.... 위 문제점을 보면 굉장히 비효율적으로 보인다. 하지만 스프링 컨테이너를 사용했을 때 이 단점들을 다 무마시킬 수 있다....!
4. 스프링 컨테이너에서의 Singleton
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 Singleton으로 관리한다. 지금까지 우리가 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이다. 개념과 장점을 살펴보자.
- 스프링 컨테이너는 싱글턴 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
- 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라 한다.
- 스프링 컨테이너의 이런 기능 덕분에 싱글턴 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수 있다.
- 싱글턴 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.
- DIP, OCP, 테스트, private 생성자로부터 자유롭게 싱글톤을 사용할 수 있다.
Annotation으로 스프링 컨테이너에 등록한 빈들이 싱글톤으로 사용되고 있는지 위와 같은 테스트 코드로 확인해보자!
참고로!! 스프링의 기본 빈 등록 방식은 싱글톤이지만, 싱글톤 방식만 지원하는 것은 아니다. 요청할 때 마다 새로운 객체를 생성해서 반환하는 기능도 제공한다.(자세한 내용은 빈 스코프에서)
5. Singleton의 주의점
싱글턴 패턴이든, 스프링 같은 싱글턴 컨테이너를 사용하든 객체 인스턴스를 하나만 생성해서 공유하는 싱글턴 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글턴 객체는 상태를 유지(stateful)하게 설계하면 안된다. 즉 무상태(stateless)로 설계해야 한다!
만약 무상태로 설계하지 않는다면 어떤 일이 발생할까? 예제를 통해서 확인 해보자. 주문 서비스를 A, B 고객이 사용하는 시나리오이며, 주문 서비스를 싱글턴 유지 설계했을 때 결과를 확인해보자.
위에서 싱글턴의 문제점을 발견할 수 있다. A, B 고객이 각각 주문을 개별로 진행하고 A 고객의 주문 내역을 확인했는데 B고객의 주문 내역이 출력됐다. 특정 클라이언트에 의존적인 필드(price)를 하나로 공유해버리니까 B 고객이 주문하자마자 주문 내역에 덮어쓰여져 버린 것이다. 이런 상황을 맞이할 수 있기 때문에 꼭!! 무상태 설계를 해야한다! 무상태 설계 조건을 확인하고 조건에 맞게 다시 싱글턴 방식으로 코드를 작성해보자!
무상태(stateless) 설계 조건
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야 한다.(쓰기 지양)
- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
- 스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있다!
위처럼 필드로 주문 내역을 선언하지 않고 파라미터를 활용해 주문 내역을 담았다. 이렇게 되면 고객들의 주문 내역을 개별적으로 가질 수 있게 된다!
!!김영한 강사님 강의
'스프링 > SpringBasicCore' 카테고리의 다른 글
Section7(1편) 의존관계 자동 주입 종류 (3) | 2024.03.08 |
---|---|
Section6 Component Scan (0) | 2024.02.26 |
Section4(3편) BeanDefinition (1) | 2024.02.20 |
Section4(2편) BeanFactory와 ApplicationContext (0) | 2024.02.07 |
Section4(1편) 스프링 컨테이너와 스프링 빈 (1) | 2024.02.07 |