싱글턴 패턴
- 싱글턴 패턴은 인스턴스를 하나만 생성되게 한다.
- 싱글턴 패턴에도 문제점이 있는데, 다음과 같음
- 의존관계상 클라이언트가 구체 클래스에 의존한다.
- 테스트 하기 어렵다.
- 내부 속성을 변경하거나 초기화하기 어렵다.
- private 생성자로 자식 클래스를 만들기 어렵다.
싱글턴 컨테이너
- 스프링 컨테이너는 싱글턴 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글턴으로 생성함을 보장해준다.
- 스프링 컨테이너는 싱글턴 패턴을 적용하지 않아도 알아서 싱글턴으로 객체 인스턴스를 관리해준다.
싱글턴 사용 시 주의점: 동기화 이슈
싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는
싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를
유지(stateful)하게 설계하면 안된다. 무상태(stateless)로 설계해야 한다!
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다!
- 가급적 읽기만 가능해야 한다.
- 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
@Configuration의 역할
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
System.out.println("call : AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository 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 FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
AnnotationConfigApplicationContext로 파라미터로 넘긴 값인 AppConfig 자체도 스프링 빈에도 등록이 된다.
이 AppConfig 스프링 빈을 출력해보자. 아래와 같이 테스트하면 -> 엥?
예상 결과 : 순수 클래스인 class hello.core.AppConfig
실제 결과 : 정체 모를 클래스 class hello.core.AppConfig$$EnhancerBySpringCGLIB$$bd479d70
이상한 클래스가 튀어 나온다.
@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
}
출력
bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$bd479d70
그 이유는 @Configuration에 있다!
스프링이 CGLIB(시지립)이라는 바이트 코드 조작 라이브러리를 사용하여 AppConfig 대신 이 AppConfig을 상속받은 임의 클래스를 만들어서 그 클래스를 빈에 등록한다. 이 이유는 무엇일까? 바로 싱글턴을 보장해주기 위해서다.
스프링 빈 등록시 아래와 같이 AppConfig에 작성된 클래스가
오직 한번씩만 호출되는 것을 볼 수 있다. 즉 싱글턴이 보장된다.
call : AppConfig.memberService
call : AppConfig.memberRepository
call : AppConfig.orderService
그럼 @Configuration을 지우면?
출력 결과 -> 처음 예상했던대로 AppConfig 클래스가 그대로 출력되어 나옴
bean = class hello.core.AppConfig
또 다른 출력 결과 -> 각 클래스가 싱글턴으로 생성되지 않음!
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.orderService
call AppConfig.memberRepository
call AppConfig.memberRepository
*여기서 memberRepository가 3번 호출된 이유는?
1번은 @Bean에 의해 스프링 컨테이너에 등록하기 위해서이고,
2번은 각각 다른 클래스에서 memberRepository() 를 호출하면서 발생한 코드다.
이때 이 세개의 인스턴스 모두 주소 값이 다르다. 즉 다른 인스턴스다.
결론 : 큰 고민 없이 스프링 설정 정보에는 항상 @Configuration을 붙이자!
------------------------
참고 : 인프런 김영한님 강의(스프링 핵심원리 기본편)
댓글