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으로 선언해서 외부에서 사용하지 못하도록 막아야한다.
- 객체를 2개 이상 생성하지 못하도록 막아야 한다.
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을 사용해야 한다.
출처 : [인프런 김영한 스프링 핵심 원리 - 기본편]
스프링 핵심 원리 - 기본편 강의 | 김영한 - 인프런
김영한 | 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보
www.inflearn.com