강의를 들으며 생각 정리
대부분의 스프링 애플리케이션은 웹 애플리케이션이다. 웹 애플리케이션은 고객이 동시에 계속 요청하는 시스템이다. 그렇다면 매 요청 때마다 객체를 만들어야 할까? 이는 지나친 메모리 사용을 통해 비효율적이다. 이 때문에 싱글톤 컨테이너 사용이 필요하다.
싱글톤 패턴
전에 만들었던 스프링 없는 순수한 DI 컨테이너인 AppConfig는 요청을 할 때마다 객체를 새로 생성한다. 고객 트래픽이 초당 100이 나오면 초당 100개 객체가 생성되고 소멸된다. 이는 심한 메모리 낭비를 초래한다. 해결방안은 해당 객체가 딱 1개만 생성되고, 공유하도록 설계하면 된다.
싱글톤 패턴은 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.
싱글톤 패턴을 적용한 클래스 객체를 보자.
public class SingletonService {
private static final SingletonService instance=new SingletonService();
public static SingletonService getInstance(){
return instance;
}
private SingletonService(){
}
public void logic(){
System.out.println("싱글톤 객체 로직 호출");
}
}
- static 영역에 객체를 딱 1개만 생성한다.
- 이 객체 인스턴스는 오직 getInstance() 메소드를 통해서만 조회할 수 있다.
- 생성자를 private로 막아서 혹시라도 외부에서 객체 인스턴스가 생성되는 것을 막는다.
그러나 싱글톤 패턴은 수 많은 문제점들을 가지고 있다.
- 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
- 클라이언트가 구체 클래스에 의존한다. (ex. getInstance시 구체 클래스의 메소드로서 사용, DIP 위반)
- private 생성자로 자식 클래스 생성이 어려운 등 유연성이 떨어진다.
싱글톤 컨테이너
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 따로 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다. 지금까지 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이다.
스프링 컨테이너 덕분에 고객의 요청이 올 때마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 재사용할 수 있다. 그러나 여기에 몇 가지 주의점이 따른다.
싱글톤 방식의 주의점
싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지하게(stateful) 설계하는 것이 아닌 무상태로(stateless) 설계해야 한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 필드 대신에 자바에서 공유되지 않는, 지역변수 등을 사용해야 한다.
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를 사용하고 this.price=price 부분에서 계속 price를 업데이트 해주기 때문에 각 클라이언트 만의 price를 유지할 수가 없다.
@Configuration
AppConfig.java
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemoryMemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(),discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
return new RateDiscountPolicy();
}
스프링 컨테이너는 스프링 빈이 싱글톤이 되도록 보장해준다. 그런데 AppConfig를 보면 MemberService는 MemberServiceImpl 객체를 생성하면서 그 안에 memberRepository를 생성한다. OrderService 또한 memberRepository 객체를 같이 생성한다. 결과적으로 각각의 memberRepository가 생성되면서 싱글톤이 깨지는 것처럼 보인다. 그러나 실제로 스프링 컨테이너는 이러한 중복을 방지해준다.
그 이유는 @Configuration을 적용한 AppConfig에 있다. 실제로 스프링 컨테이너에 올라간 AppConfig 빈의 class 이름을 찍어보면 그냥 AppConfig가 아닌 다음과 같이 출력된다.
bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$b794d3c8
클래스 명에 CGLIB이 붙으면서 상당히 복잡해진 것을 볼 수 있다. 이는 내가 만든 클래스가 아닌 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것이다. @Configuration에 의한 CGLIB 클래스가 바로 싱글톤이 보장되도록 해준다.
AppConfig@CGLIB 예상 코드
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
return 스프링 컨테이너에서 찾아서 반환;
} else { //스프링 컨테이너에 없으면
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환
}
}
@Bean 메서드마다 스프링 컨테이너에 스프링 빈이 이미 존재하면 존재하는 빈을 반환하고, 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반환되는 코드가 동적으로 만들어진다. 덕분에 싱글톤이 보장되는 것이다.
정리하자면, @Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다. @Configuration을 사용함으로써 싱글톤을 보장할 수 있다.
'java > spring' 카테고리의 다른 글
[Spring] 의존관계 자동 주입 (0) | 2021.01.11 |
---|---|
[Spring] 컴포넌트 스캔 (0) | 2021.01.08 |
[Spring] 스프링 설정하기, 스프링 컨테이너와 스프링 빈 (0) | 2021.01.08 |
[Spring] 스프링 핵심 원리 이해2 - 객체 지향 원리 적용 (0) | 2021.01.06 |
[Spring] 스프링 핵심 원리 이해 1 - 예제 만들기 (0) | 2021.01.06 |