목표

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

목표

1. 스프링 빈 조회 방법을 배운다.
2. BeanFactory와 ApplicationContext 의 차이를 배운다.

'스프링 컨테이너와 스프링 빈' 목차

1. 스프링 컨테이너 생성  

2. 컨테이너에 등록된 모든 빈 조회

3. 스프링 빈 조회 - 기본

4. 스프링 빈 조회 - 동일한  타입이 둘 이상 (이번 포스팅)

5. [ 중요 ] 스프링 빈 조회 - 상속 관계

6. BeanFactory와 ApplicationContext 

7. 다양한 설정 형식 지원 - 자바 코드, XML

8. 스프링 빈 설정 메타 정보 - BeanDefinition


4. 스프링 빈 조회 - 동일한 타입이 둘 이상 

1) 타입으로 조회 시, 같은 타입이 2개 이상 있으면 중복 오류가 발생한다. 

임시로 MemberRepository 타입 빈이 2개 있는 SameBeanConfig.class 설정 파일을 만들었다. 

여기서 MemberRepository.class 타입 빈을 가져오면? 

아래와 같은 NoUniqueBeanDefinitionException 이 발생한다. 설명이 굉장히 친절하다.

싱글 매칭 빈을 기대했는데, 2개 였다는 내용. 

org.springframework.beans.factory.NoUniqueBeanDefinitionException
: No qualifying bean of type 'hello.corebasic.member.MemberRepository' available
: expected single matching bean but found 2: memberRepository1,memberRepository2

 

이렇게 특정 에러가 발생하는지 확인하려면 assertThrow와 Exception 클래스명을 써서 확인한다.

2) 타입으로 조회 시, 같은 타입이 2개 이상 있으면 빈 이름을 지정하면 된다!

빈 이름은 중복 불가다. 빈 이름을 안겹치게 지어서 조회하면 된다.

3) 특정 타입을 모두 조회하기

getBeansOfType() 메서드로 Map<이름, 타입> 특정 타입의 모든 빈을 모두 조회할 수 있다. 

나중에 @Autowired같이 자동으로 주입되는 기능을 배울 때 이런식으로 적용이 된다. 

ac.getBeansOfType(MemberRepository.class);


[ 중요 ] 5. 스프링 빈 조회 - 상속 관계 

1) 부모 타입으로 빈을 조회하면, 자식 타입도 함께 조회한다.

스프링 빈 대 원칙 하나는 "스프링 빈 하나 조회하면? 그 빈의 자식을 싹다 조회한다. " 

그래서 모든 자바 객체의 최고 부모인 Object 타입. Object 타입으로 조회하면, 모든 스프링 빈을 조회한다. 

테스트 코드로 확인하자. TestConfig 설정파일을 하나 만들자. DiscountPolicy 이름으로 생성될 빈이 2개다!  반환되는 생성자만 다르다. 

2) 부모 타입을 조회하면, 자식까지 다 딸려서 조회되니까 자식이 둘 이상인 경우 오류가 발생한다. 

이럴 땐 이름을 지어서 구분하면 된다. 

3) 부모 타입을 조회 시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다. 

rateDiscountPolicy 특정 빈 이름으로 조회하자. 

4) 특정 하위 타입으로 조회할 수 있다. 

구체적인 타입으로 딱 지정하면 조회할 수 있지만 안 좋은 방법이다. 

5) 부모 타입으로 모두 조회해보자. 

할인 정책 인터페이스를 구현한 구현체 클래스 2개가 조회 된다. 

rateDiscountPolicy, fixDiscountPolicy 자식 타입 스프링 빈 2개가 출력됨을 확인할 수 있다. 

[ 참고 ] 실무에서 테스트 코드를 작성할 때는 이렇게 출력물을 만들면 안된다

지금은 공부용이라서 system.out.print를 하지만, 실무에서 테스트 코드를 작성할 때는 이렇게 출력물을 만들면 안된다. 

테스트 통과/실패 여부만 판단하도록 작성하는게 좋다. 

 

6) 최고 부모 타입인 Object 로 조회해보자. 

모든 자바 객체의 부모는 Object 라서. 모든 스프링 빈이 조회된다. 

7) 부모 타입으로 조회하면, 자식 타입도 함께 조회한다

실무에서는 ApplicationContext를 통해 getBean() 빈을 조회할 일이 거의 없다. 스프링 컨테이너가 자동으로 의존관계를 주입해주기 때문이다.

굳이 이렇게 배운 이유는, 빈 조회는 스프링의 기본 기능이기도 하고 아주 가끔 순수한 자바 애플리케이션에서 스프링 컨테이너를 생성해서 사용할 일이 있기 때문이다. 
그리고 부모 타입으로 조회 시, 자식 타입 빈까지 조회됨을 알고 있어야 자동 의존관계 주입을 배울 때 잘 사용할 수 있다.


6. BeanFactory 와 ApplicationContext

BeanFactory 와 ApplicationContext에 대해서 알아보자. 아래 계층 구조를 보자.

  • 최상위에 있는 인터페이스 BeanFactory 가 있다.
  • 최상위에 있는 인터페이스 BeanFactory를 상속받은 인터페이스 ApplicationContext 가 있다. 
  • ApplicationContext 는 BeanFactory에 부가 기능을 더한 것이다. 

BeanFactory

  • BeanFactory 는 스프링 컨테이너의 최상위 인터페이스다. 
  • 스프링 빈을 관리하고 조회하는 역할을 담당한다. 지금까지 빈 조회에 사용한 getBean() 을 BeanFactory이 제공한 것이다. 

ApplicationContext 

  • BeanFactory의 모든 기능을 상속 받아서 제공한다. 

BeanFactory와 ApplicationContext의 차이는 뭘까? 

  • 애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고 수 많은 부가기능이 필요하다. 
  • ApplicationContext는 아래와 같이 여러 인터페이스를 상속받아서 국제화, 이벤트, 리소스 조회 등의 기능을 제공한다. 
  • 우리는 BeanFactory를 직접 사용할 일은 거의 없고, 빈 관리와 부가기능이 포함된 ApplicationContext를 사용한다. 

메시지 소스를 활용한 국제화 기능 

 : 예를 들어 한국에서 접속하면 한국어로, 영어권에서 접속하면 영어로 출력해준다 

환경 변수 

 : 로컬, 개발, 운영 등을 구분해서 환경 변수를 처리 

애플리케이션 이벤트

 : 이벤트를 발행하고 구독하는 모델을 편리하게 지원 

편리한 리소스 조회 

 : 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

 

BeanFactory 나 ApplicationContext를 스프링 컨테이너라 한다. 


다음 강의에서는 '스프링 컨테이너가 지원하는 자바코드, XML 형식의 설정 정보'를 배운다. 

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

728x90

+ Recent posts