private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야 한다.public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
//private 생성자
private SingletonService(){}
}
@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonServiceTest(){
//private 으로 생성자를 막아두었다. 컴파일 오류가 발생한다.
//new SingletonService();
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
Assertions.assertThat(singletonService1).isSameAs(singletonService2);
}
static 영역에 객체 인스턴스를 미리 하나 생성해서 올려둔다.new 키워드로 객체 인스턴스가 생성되는 것을 막는다.@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
//참조값이 같은 것을 확인
Assertions.assertThat(memberService1).isSameAs(memberService2);
}
💡 싱글통 방식의 주의점
@Configuration
public class AppConfig {
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public MemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public MemberServiceImpl memberService(){
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderServiceImpl orderService(){
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
AppConfig 를 보면 분명 memberRopository 가 3번 호출되어야 하는 것이 맞다.
하지만 테스트를 해보면 3번이 아니라 1번만 불린다.
그 이유는 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용하기 때문이다.
@Test
void configurationDeep() {
ApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);
//AppConfig도 스프링 빈으로 등록된다.
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$bd479d70
순수한 클래스라면 class hello.core.AppConfig 로 출력되야하나, xxxCGLIB 가 붙었다.
스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것이다.
@Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다. 그래서 싱글톤이 보장되는 것이다.
만약 @Configuration 을 적용하지 않고, @Bean 만 적용하면 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다. 스프링 설정 정보는 항상 @Configuration 을 사용하는게 좋다.