스프링 핵심 원리 (3) - 싱글톤 컨테이너
1. 스프링 컨테이너의 이점 (싱글톤)
1.1 앱 어플리케이션과 싱글톤
- 스프링은 기업용 온라인 서비스 기술을 지원하기 위해 탄생
- 스프링 어플리케이션은 대부분 웹 어플리케이션임
- 웹 어플리케이션은 보통 여러 고객이 동시 요청한다.
AppConfig appConfig = new AppConfig()
MemberService memberService1 = appConfig.memberService();
MemberService memberService2 = appConfig.memberService();
- 설정 클래스인 순수 DI 컨테이너 AppConfig를 사용해서 memberService를 생성해보자
- memberService()메서드는 내부적으로 생성자를 사용하여 구현체를 생성하므로, 요청시마다 새로운 MemberService를 생성할 것이다.
- 메모리 낭비가 심하다.
-> 싱글톤 패턴으로 이러한 문제점을 해결할 수 있다.
1.2 싱글톤 패턴
- 클래스 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.
객체 인스턴스가 2개 이상 생성되지 못하도록 막아야한다.
- 미리 생성된 객체를 참조해서 사용하기 때문에 메모리 낭비를 막을 수 있다.
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static getInstance(){ return instance;}
//..로직들
}
- static 키워드를 통해 JVM runtime 메모리 중 메서드 영역에 객체 생성 후 저장
- getInstance()가 호출되면, 미리 만들어둔 instance return
*싱글톤 패턴을 구현하는 방법은 여러가지가 있다.! 위에 적용된 것은 그 중 가장 단순하고 안전한 것
이러한 순수 자바코드로 작성한 싱글톤 패턴은 다음과 같은 문제가 있다.
- > 싱글톤 패턴 구현 코드 자체가 귀찮다.
- > 의존 관계상 클라이언트가 구체 클래스에 의존하게 된다. -> DIP, OCP 위반 가능성 높다.
(메모리 적재시 한번만 객체를 생성해야하고, 생성자도 한번만 호출해야하므로, 의존관계 주입이 어렵다)
- > 내부 속성변경 어렵고, 상속도 어렵다.
- > 결론적으로 유연성이 매우 떨어진다.
1.3 싱글톤 컨테이너
- 스프링 컨테이너는 싱글톤 컨테이너로 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다.
- 스프링 빈이 싱글톤으로 관리된다.
* 싱글톤의 장점만 이용할 수 있게 해준다. 순수 DI컨테이너를 사용하지 않고, 스프링 컨테이너를 사용하므로 얻을 수 있는 큰 장점 중 하나이다.
스프링 컨테이너 - 싱글톤 컨테이너
- 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리
- 스프링 컨테이너 처럼 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라 한다.
- 스프링 컨테이너의 이러한 기능 덕분에 단점 없이 객체를 싱글톤으로 유지할 수 있다.
* 기본적으로 스프링 컨테이너는 싱글톤으로 동작하는게 맞지만, 빈의 생명주기를 바꿔줄 수도 있다.
1.4 ** 싱글톤 방식의 주의점 **
- 싱글톤 객체는 상태를 유지하는 설계를 하면 안된다. 무상태(stateless)로 설계해야함
- 특정 클라이언트에 의존적 필드 (특정 클라이언트가 싱글톤 객체 필드 변경) 있어선 안된다.
- 가급적 읽기만 가능하게 하고, 지역변수, ThreadLocal등을 사용하자
- 스프링 빈의 필드에 공유 값을 설정하면 아주 큰 장애가 발생할 수 있다.
- 따라서 스프링 빈은 무상태로 설계하자.
2. @Configuration과 싱글톤
- 스프링 빈을 싱글톤으로 유지할 수 있는 이유는 @Configuration덕분이다.
- 스프링은 싱글톤 레지스트리로 스프링 빈이 싱글톤으로 유지되도록 보장하기 위해
바이트코드를 조작하는 라이브러리를 사용한다.
이러한 라이브러리를 설정 코드에 적용하기 위해 @Configuration 어노테이션이 필요하다.
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " +bean.getClass());
// bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$~
//@Configuration 어노테이션 제거 후
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " +bean.getClass());
// bean = class hello.core.AppConfig
- 어노테이션을 붙이냐 안붙이냐에 따라 Appconfig객체가 스프링 컨테이너에 다르게 등록된다.
- 여기서 CGLIB가 바이트 코드 조작 라이브러리로 어노테이션이 붙어있으면, 이를 이용해서 AppConfig클래스를
상속받은 임의의 다른 클래스를 만들고, 이를 스프링 빈으로 등록한다.
- 임의의 클래스가 싱글톤이 되도록 보장해준다.
- AppConfig를 상속받아 내부 메서드를 싱글톤을 보장하도록 오버라이딩한다.
- 스프링 컨테이너는 오버라이딩 된 메서드를 호출해서 빈을 등록하므로, 싱글톤이 보장된다.
* @Configuration 어노테이션을 사용하지 않으면, 스프링 컨테이너에 빈으로 등록은 되지만,
싱글톤을 보장받지 못하고, 스프링 컨테이너에서 관리해주지도 않는다.
참고자료: 스프링 핵심 원리 (김영한)