싱글톤 컨테이너
웹어플리케이션의 특성과 싱글톤의 필요성
웹 어플리케이션은 여러 유저가 끊임없이 많은 요청을 보냅니다. 따라서 스프링을 적용하기전 AppConfig의 구조대로라면 아래와 같이 요청마다 객체를 생성하게 됩니다. 이렇게 하면 메모리 낭비가 심하게 됩니다. 따라서 하나의 객체를 만들어서 공유하는 형태로 개선을 해야합니다.
테스트 코드로 간단하게 항상 다른 객체가 할당되는지 확인해보면 아래와 같습니다. 스프링을 사용하지 않고 그냥 AppConfig 인스턴스를 생성해서 memberService를 받아쓰기때문에 싱글톤 보장x
public class SingletonTest {
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainer() {
AppConfig appConfig = new AppConfig();
//1. 조회 : 호출할 때 마다 객체를 생성
MemberService memberService1 = appConfig.memberService();
//2. 조회: 호출할 때 마다 객체를 생성
MemberService memberService2 = appConfig.memberService();
//참조값이 다른 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
assertThat(memberService1).isNotSameAs(memberService2);
}
}
출력값은 아래와 같이 다르게 나왔고 assertThat 또한 다른 것으로 통과!
memberService1 = hello.core.member.MemberServiceImpl@13b6aecc
memberService2 = hello.core.member.MemberServiceImpl@158a8276
싱글톤 직접 구현하기
싱글톤 패턴은 클래스의 인스턴스가 딱 1개만 생성되도록 보장하는 패턴입니다. 따라서 아래처럼 클래스 내부에서 private static final을 이용해서 하나의 객체 instance를 생성해줍니다. 그리고 밖에서는 getInstance함수를 이용해서 객체를 받아쓸 수 있도록 해줍니다. 또한 외부에서 new를 하지 못하도록 private로 생성자를 선언해줍니다.
public class SingletonService {
//1. static 영역에 객체를 딱 1개만 생성해둔다.
private static final SingletonService instance = new SingletonService();
//2. public으로 열어서 객체 인스터스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용한다.
public static SingletonService getInstance() {
return instance;
}
//3. 생성자를 private으로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
private SingletonService() {
}
}
이제 위에 클래스를 이용해서 테스트를 진행해보겠습니다.
@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonServiceTest(){
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
//참조값이 같은 것을 확인
System.out.println("singletonService1 = " + singletonService1);
System.out.println("singletonService2 = " + singletonService2);
// isSameAs : 객체비교, isEqualAs : 값비교
assertThat(singletonService1).isSameAs(singletonService2);
}
출력값은 아래와 같이 같은 객체로 나왔습니다
singletonService1 = hello.core.singleton.SingletonService@66d18979
singletonService2 = hello.core.singleton.SingletonService@66d18979
싱글톤 패턴 직접구현의 단점
싱글톤패턴을 구현하는 방법은 여러가지가 있습니다. 방금과 같은 방식은 일단 유연성이 매우 떨어집니다. 생성자가 private이기 때문에 자식 클래스를 만들기 어렵고 테스트도 어려우며 구체클래스에 의존하게 됩니다. 이런 문제를 해결해주면서 싱글톤 패턴을 보장해주는 방법이 바로 스프링을 적용하는 것입니다✨✨
스프링으로 싱글톤 패턴 적용하기
스프링은 설정파일을 보고 스프링빈을 생성 및 관리하는데요. 이때 싱글톤 패턴을 반영하여 관리해줍니다.
테스트 코드
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// 꺼내기
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
//참조값이 같은 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
assertThat(memberService1).isSameAs(memberService2);
}
출력값은 아래와 같이 같은 객체인 것을 알 수 있습니다. 스프링 컨테이너에 스프링빈들을 유일하게 등록하고 필요할 때마다 스프링 컨테이너에서 받아 쓰기때문에 공유할 수 있습니다.
memberService1 = hello.core.member.MemberServiceImpl@bf1ec20
memberService2 = hello.core.member.MemberServiceImpl@bf1ec20
이제 최종적으로 싱글톤이 보장되기 때문에 처음에 유저의 요청을 처리하던 그림은 아래와 같이 바뀝니다.