[Spring] 스프링의 싱글톤
- [ Backend ]/Spring
- 2022. 1. 22.
개요
스프링을 이용하는 웹 애플리케이션의 경우 일반적으로 여러 클라이언트의 요청이 동시에 이루어진다. 그런데 싱글톤 방식을 사용하지 않는 컨테이너의 경우 고객의 요청이 올 때마다 객체를 새로 생성해야 한다. 이 대신 객체를 단 1개만 생성하고 공유하도록 하면 자원 낭비를 줄일 수 있다.
싱글톤 패턴
클래스의 인스턴스가 단 한개만 생성되도록 하는 디자인 패턴이다. 결국 한 개의 객체를 공유하도록 만들어 주어야 하는데, 이를 위해서
1. private static final 객체를 1개 생성후 static메서드를 통해서만 조회하게 한다.
2. 생성자는 private로 선언해서 외부에서 new키워드를 사용하지 못하게 한다.
와 같은 방법을 사용할 수 있다.
static메서드를 통해서만 조회하도록만을 제한해도, main메소드에서 new키워드를 통해서 또 다시 생성하는 것은 막지 못한다. 따라서 생성자의 무분별한 호출을 컴파일 오류로 제한할 방법이 필요한데, 이것을 위해서 생성자를 private로 선언해주면 된다.
그렇게 하면 static영역에 선언된 객체를 이용하기 위해서는 별도의 static 메소드를 이용하는 방법 말고는 접근 방법이 존재하지 않게 되고, 결국 객체의 생성은 막으면서 기능의 제한은 이루어지지 않게 된다. 이러한 방법을 싱글톤 패턴이라고 한다.
public class SingletonService {
private static final SingletonService instance - new SingletonService();
public static SingletonService getInstance() {
return instance;
}
public void logic() {
//single service logic
}
}
다만 이러한 방법은 구현하는데 추가적인 코드가 필요하고, 클라이언트가 구체 클래스에 의존하기 때문에 DIP와 OCP와 같은 객체지향 설계 원칙을 위반할 가능성이 높으며 유연한 테스트 코드 작성에도 불편함이 있다. 하지만 스프링의 경우 이러한 문제점들을 기본적으로 해결하면서 기본적으로 싱글톤으로 관리해준다.
(자바의 싱글톤 패턴: https://eckrin.tistory.com/91)
스프링의 싱글톤 패턴
스프링 컨테이너는 기본적으로 객체 인스턴스들을 싱글톤으로 관리해준다. 또한 기존의 직접 구현한 싱글톤 패턴과 다르게 DIP와 OCP위반과 같은 단점들을 감수하지 않고서 자유롭게 싱글톤을 사용할 수 있다.
public class SingletonService {
void springContainerTest() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberservice1 = ac.getBean("memberService", MemberService.class);
MemberService memberservice2 = ac.getBean("memberService", MemberService.class);
assertThat(memberService1).isSameAs(memberService2);
}
}
위와 같이 스프링 컨테이너를 사용해서 객체를 뽑아보면 기본적으로 클라이언트의 요청이 올때마다 이미 만들어진 객체를 공유하게 된다.
다만 싱글톤 방식으로 설계하면 인스턴스는 상태를 유지하게 설계하면 안된다. 만약에 상태를 유지하게 설계하면, 공유되는 필드의 값이 변경되면 인스턴스를 공유하는 모든 코드들이 영향을 받게 된다. 따라서 싱글톤 방식으로 설계할 때, 공유필드 설정은 가급적 자제하자. 가급적이면 읽기만 허용하도록 설계하는 것이 가장 좋지만, 그렇지 못하더라도 특정 상황에서 접근되는 값은 바로 반환하는 식으로 코드를 작성하자.
(jpa에서 setter사용을 자제하는 이유와 유사한 것 같다)
스프링 컨테이너는 동일한 요청이 여러개 와도 같은 객체 인스턴스의 스프링 빈을 반환한다.
@Configuration에서의 싱글톤
Configuration 어노테이션은 어플리케이션 설정정보를 세팅하는 클래스에 명시해준다. 이때 코드상에서 싱글톤을 깨트리는 것처럼 호출이 이루어질 수도 있는데, 이 스프링 어노테이션이 그러한 현상을 막아준다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
위 코드를 보면 memberService 빈과 orderService빈 모두에서 memberRepository()를 호출하고, 각각 new키워드로 MemoryMemberRepository()객체가 생성되는 것처럼 보인다. 하지만 스프링을 이용하면 이러한 경우에도 싱글톤이 보장된다.
사실 스프링은 @Configuration이 @파라미터로 넘긴 클래스들 전체를 빈으로 등록하는 것이 아니라, 스프링 빈에 이미 존재하는 이름이면 존재하는 빈을 반환하고, 스프링 빈이 없으면 클래스를 상속하는 동일한 이름의 조작 클래스을 생성해서 스프링 빈으로 등록하고 반환하는 코드를 동적으로 생성한다. 그리고 그 조작 클래스에는 싱글톤 패턴의 유지를 위한 (동일한 이름의 빈이 이미 스프링 컨테이너에 등록되어 있다면 기존에 등록된 스프링 빈을 사용하도록) 로직이 짜여있기 때문에 이러한 케이스에도 싱글톤을 보장할 수 있다.
(또한 빈의 이름이 unique해야하는 이유가 이러한 기능들을 위함이라는 것을 짐작할 수 있다)
//AppConfig를 상속한 조작 메소드(AppConfig@CGLIB) 내의 로직
@Bean
public MemberRepository memberRepository() {
if(memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있다면)
return 스프링 컨테이너에서 반환
else { //스프링 컨테이너에 없다면
(기존 로직 호출)
return new MemoryMemberRepository();
}
}
이렇게 스프링은 CGLIB라는 바이트코드 조작 라이브러리를 사용하여 AppConfig를 사용받은 임의의 다른 클래스를 만들어서 그 클래스를 스프링 빈으로 등록한다. 일종의 프록시 패턴을 적용했다고도 볼 수 있겠다. 결론적으로, 스프링은 반드시 싱글톤을 보장해준다는 사실을 알 수 있었다.
'[ Backend ] > Spring' 카테고리의 다른 글
[Spring] 빈 스코프 (0) | 2022.01.27 |
---|---|
[Spring] NoUniqueBeanDefinitionException (0) | 2022.01.25 |
[Spring] IoC, 컴포넌트 스캔, 의존관계 주입 (0) | 2022.01.24 |
[Spring] @Autowired vs @PersistenceContext (0) | 2022.01.23 |
[Spring] 스프링 컨테이너, 스프링 빈 (0) | 2022.01.22 |