Spring/[인프런 김영한 스프링 핵심 원리 -기본편]

[인프런 김영한 스프링 핵심 원리 - 기본편] 싱글톤 컨테이너

h2boom 2024. 8. 13. 21:16

싱글톤 

Config config = new Config();
MemberService memberService1 = config.memberService();
MemberService memberService2 = config.memberService();
  • 스프링 없는 순수한 DI 컨테이너 Config 클래스의 경우 
    • 호출할 때마다 새로운 객체를 생성해야한다.
    • 메모리 낭비가 심하다는 단점이 있다.
      해결 방안으로 객체는 딱 1개만 생성되고 이후 공유하도록 설계하는 것.

 

 

  • 싱글톤 패턴 (Singleton Pattern) : 클래스의 인스턴스(객체)가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.
    • 객체를 2개 이상 생성하지 못하도록 막아야 한다.
      • 생성자를 private으로 선언해서 외부에서 사용하지 못하도록 막아야한다. 

 

public class SingletonService {
    //자기 자신의 객체를 private static 변수로 1개만 만들어진다.
    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance() {
        return instance;
    }

    private SingletonService() {} //private으로 생성자를 선언함으로 외부에서 호출하지 못하도록 막는다.
}
  • 위 예제는 싱글톤 패턴을 적용한 클래스의 예제이다.
    • static final로 자신의 객체를 미리 생성해놓는다.
    • 객체가 필요한 경우 getInstance() 메소드를 통해서만 조회할 수 있고 항상 같은 객체를 반환한다.
    • 객체는 1개만 존재해야하므로 객체를 추가로 더 생성하지 못하게 생성자를 private으로 선언한다.
    • 예제의 방식 외에도 싱글톤 패턴을 구현하는 방법이 여러가지가 있다.

 

  • Assertions.assertThat().isSameAs() 는 == 비교와 같고 isEqualTo()는 equals()와 같다.

 

  • 싱글톤 패턴의 문제점
    • 싱글톤 패턴을 구현하는 코드 자체가 많다.
    • 의존관계상 클라이언트가 구체 클래스에 의존해야한다 (DIP 원칙 위반)
    • 구체 클래스에 의존하다보면 OCP 원칙을 위반할 가능성이 높아진다.
    • private 생성자로 자식 클래스를 만들기 어렵다.
    • 테스트 하기 어렵고 내부 속성을 변경, 초기화하기 어렵기에 유연성이 떨어진다.

싱글톤 컨테이너 (스프링 컨테이너)

  • 스프링 컨테이너는 싱글톤 패턴으로 문제점을 해결하면서 객체를 싱글톤으로 관리한다.
    • 스프링 빈 = 싱글톤으로 관리되는 빈

 

  • 싱글톤 컨테이너 : 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도 객체를 싱글톤으로 관리한다.
    • 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다.
  • 싱글톤 레지스트리 : 싱글톤 객체를 생성하고 관리하는 기능

 

 

  • 스프링의 기본 빈 등록 방식은 싱글톤이지만 싱글톤 방식만 지원하지는 않는다.
    • 거의 99% 싱글톤 방식을 사용한다.

싱글톤 방식의 주의점

  • 싱글톤 방식의 주의점
    • 하나의 같은 객체를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다.
  • 싱글톤 방식은 무상태(stateless)로 설계해야한다.
    • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
    • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
    • 가급적으로 읽기만 가능해야 한다.
    • 필드 대신 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.

 

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;
	}
}
  • 예제의 price 변수는 공유 변수이다.
    • 스레드1 order() 호출 -> 스레드2 order() 호출 -> 스레드1 getPrice() 호출하는 경우 올바르지 않은 결과가 나올 수 있다.
    • stateful한 위의 예제를 stateless하게 바꿔주려면 공유 변수를 제거하고 order() 메소드에서 바로 return price;를 하도록 만들 수 있다.

 

  • 공유 필드는 항상 조심해야하며 스프링 빈은 항상 무상태(stateless)로 설계해야한다.

@Configuration과 싱글톤

@Configuration
public class AppConfig {
	@Bean
	public Repository repository(){
		return new RepositoryImpl();
	}
...
}

ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
  • @Configuration으로 선언된 어노테이션 기반 자바 설정 클래스로 스프링 컨테이너를 생성한 예제이다.
    • 매개변수로 자바 설정 클래스를 넘길 때 스프링 빈으로 등록된다.
    • 하지만 여기서 AppConfig가 아닌 AppConfigCGLIB~라는 스프링 빈이 등록된다.
    • xxxCGLIB는 스프링이 바이트 코드 조작 라이브러리를 이용해서 원본 클래스를 상속받는 임의로 만든 다른 클래스를 의미하며 이 클래스를 스프링 빈으로 등록한다.

 

@Bean
public Repository repository(){
	if(repository가 스프링 빈으로 이미 등록되어 있으면){
		return 스프링 컨테이너에서 스프링 빈을 찾아서 반환;	
	} else { // 스프링 컨테이너에 없다면
		새로운 RepositoryImpl을 생성하고 스프링 컨테이너에 스프링 빈으로 등록;
		return 생성한 스프링 빈을 반환;
	}
}
  • 예상 AppConfigCGLIB 클래스의 코드이다. (실제로는 더 복잡함)
    • 스프링 컨테이너에 스프링 빈으로 등록되어 있는지 여부를 확인한다.
    • 등록되어 있다면 스프링 빈을 찾아서 반환해준다.
    • 등록되어 있지 않다면 새로운 객체를 생성해서 스프링 빈으로 등록하고 반환해준다.
  • 자바 설정 클래스에 @Configuration가 없이 @Bean만 사용한다면
    • 스프링 빈으로 등록은 되지만 CGLIB 기술을 사용하지 않고 싱글톤이 보장되지 않는다.

 

  • 스프링 설정 정보는 항상 @Configuration을 사용해야 한다.

출처 : [인프런 김영한 스프링 핵심 원리 - 기본편]

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

 

스프링 핵심 원리 - 기본편 강의 | 김영한 - 인프런

김영한 | 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보

www.inflearn.com