목표

1. 스프링 컨테이너가 객체 인스턴스를 싱글톤으로 관리하는 이유를 배운다.
2. 싱글톤 패턴의 문제점과 주의점을 배운다.

'싱글톤 컨테이너' 목차

1. 웹 애플리케이션과 싱글톤  (이번 포스팅)

2. 싱글톤 패턴 

3. 싱글톤 컨테이너 

4. [중요] 싱글톤 방식의 주의점  

5. @Configuration과 싱글톤

6. @Configuration과 바이트코드 조작의 마법 


1. 웹 애플리케이션과 싱글톤  

스프링은 태생이 온라인 서비스 기술을 지원하기 위해 탄생했다. 대부분의 스프링 애플리케이션은 웹 애플리케이션이다. 

웹 애플리케이션은 보통 여러 고객이 동시에 요청을 한다. 

고객 요청이 올 때마다 AppConfig가 객체를 새로 만들면? 요청이 올 때마다 인스턴스가 생성되는데.

1000개의 요청이 서버에 들어오면?? 1000개의 인스턴스가 메모리를 차지하게 된다. -> 메모리 낭비! 

해결 방안

객체를 딱 1개만 생성되도록 보장하고 이것을 공유하도록 설계하는 것이다. 

 


2. 싱글톤 패턴

1) 싱글톤 패턴으로 이 문제를 해결해보자.

싱글톤 패턴은 JVM 안에 클래스의 인스턴스가 딱 1개만 생성되는 방식이다. 

인스턴스를 2개 이상 생성하지 못하도록 막아야 한다! 코드로 확인해보자. 

핵심은 private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막는 것이다.

 

아래는 싱글톤 패턴 구현 코드다. 

public class SingletonService {

    // 1. 자기 자신을 내부에 private 으로 선언. final 이니까 딱 1번만 생성하고, 2번 생성 불가하게 만든다.
    private static final SingletonService instance = new SingletonService();

    // 2. 인스턴스 조회는 public 으로 열어둠
    public static SingletonService getInstance(){
        return instance;
    }

    // 3. 생성자를 private 으로 생성. 외부에서 new 키워드로 객체 생성 불가하도록 막음
    private SingletonService(){ 
    }

    public void logic(){
        System.out.println("싱글톤 객체 로직 호출");
    }
}

2) 싱글톤을 어떻게 구현할까? : 객체를 미리 생성하는 안전하고 단순한 방법으로 구현해보자. 

 

1. static 영역에 객체 instance를 미리 1개 생성해서 올려둔다.

    자바가 시작할 때 객체를 하나 생성해서 정적 (클래스) 영역에 상수처럼 가지고 있는 것이다. 이미 1개 생성 끝.

2. 인스턴스 조회할 수 있는 메서드를 public으로 열어뒀다.

    호출할 때 마다 최초에 1개 만들어둔 같은 객체 인스턴스가 반환된다. 

3. 인스턴스 생성은 private 으로 막아뒀다. 외부에서 new 키워드로 객체 생성 불가하게 만들었다. 

    new 키워드로 생성하려는 순간 컴파일 오류남! 세상에서 제일 좋은 오류 컴파일 오류. 

 

3) 이제 AppConfig를 전부 싱글톤 패턴으로 바꾸면 되겠네요?

-> 스프링 컨테이너는 객체 인스턴스를 싱글톤으로 관리한다. 이미 만들어진 객체를 공유한다. 

 

4) 싱글톤 패턴의 여러 문제점 

객체 마다 싱글톤 패턴 구현하는 코드 자체가 많이 들어간다. 객체 선언, 생성자 등등 구현 코드 필요 

의존관계상 클라이언트가 구체 클래스에 의존한다 -> DIP를 위반한다.

테스트하기 어렵다.

내부 속성을 변경하거나 초기화하기 어렵다.

private 생성자를 쓰니까, 자식 클래스를 만들기 어렵다. 

결론적으로 유연성이 떨어져서 안티패턴으로 불리기도한다. 


3. 싱글톤 컨테이너 

스프링 컨테이너는 싱글톤 패턴의 문제점은 싹 해결하고, 객체 인스턴스를 싱글톤으로 관리한다. 

 

1) 싱글톤 컨테이너 

스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다. 

이렇게 싱글톤 객체를 생성하고 관리하는 컨테이너를 싱글톤 레지스트리라 한다. 

싱글톤 패턴을 위한 지저분한 코드를 안 넣어도 된다. 

private생성자, DIP 위반 등의 단점을 해결하고 싱글톤을 사용할 수 있게 된다. 

 

2) 싱글톤 객체를 사용하는지 코드로 확인하자. 

MemberService.class 타입 빈을 2개 꺼내자.  참조값이 똑같은지 확인하자. 참조값이 똑같다!

3) 스프링 컨테이너 덕분에 여러개의 요청이 오더라도 이미 만들어진 객체를 공유해서 효율적으로 재사용할 수 있다. 

스프링 컨테이너는 객체를 싱글톤으로 관리한다

스프링 컨테이너는 기본 빈 등록 방식이 싱글톤이다. 

하지만 요청할 때 마다 새로운 객체를 생성해서 반환하는 기능도 제공하긴 한다. 하지만 99% 경우가 싱글톤을 쓴다.

자세한 내용은 뒤에 빈 스코프에서 배운다. 

 

4) 스프링 컨테이너의 싱글톤 패턴 확인하는 테스트 코드


[중요] 4. 싱글톤 방식의 주의점

실무에서 아래의 주의점을 안 지켜서 큰 장애가 발생하는 경우가 있다. 

스프링 컨테이너든, 싱글톤 패턴을 구현해서 사용하든
싱글톤 방식을 쓸 때 객체를 무상태로 설계해야 한다. 

 

1) 무상태(stateless)로 설계해야 한다는 의미가 뭘까? 

* 특정 클라이언트에 의존적인 필드가 있으면 안 된다. 

* 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안 된다. 

* 가급적 읽기만 가능해야 한다. 

* 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다. 

 

여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 상태유지(stateful)하게 설계하면 안 된다. 

 

2) 실무에서 자주 발생하는 문제 코드를 알아보자 

StatefulsService 의 order() 메서드 : 사용자가 주문하면 금액을 저장한다.

A사용자는 10000원, B사용자는 20000원 어치를 주문한다고 가정한다.  

사용자 A의 주문금액이 20000원으로 나온다.

StatefulService는 싱글톤 빈이니까. statefulService1 과 statefulService2 는 같은 객체다.

하나의 price 변수에 ThreadA와 ThreadB가 접근해서 값을 변경한 것이다. 

이러면 서비스 망하는거에요. 몇 년에 한 번씩 실무에서 꼭 만나는 상태유지 문제!! 이런 건 잡기도 어렵습니다.

원래는 실무를 반영하려면 멀티스레드 넣고 상속 관계 넣고 하는 복잡한 예제가 더 적절한데, 간단한 예제를 든 것이다. 

결론: 공유 필드는 조심하면서 무상태로 만들자!

 

3) 무상태 (Stateless)로 바꿔보자! 공유되지 않는 지역변수를 쓰자. 

공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.

금액을 리턴값으로 넘기자. 


다음 강의에서는 '@Configuration과 싱글톤'을 배운다. 

프로젝트 레포지토리

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

+ Recent posts