들어가기에 앞서...
스프링은 싱글톤을 최대한 보장하고자 한다. 그렇다면 그 사실을 염두하고 아래의 코드를 보자.
AppConfig.java
package hello.core;
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository()); //new MemoryMemberRepository()
}
@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()); //new MemoryMemberRepository()
}
....
}
DI 컨테이너인 AppConfig 클래스의 memberService( ), orderService( )는 memberRepository( )를 호출한다. 그리고 memberRepository는 MemoryMemberRepository()를 new 한다.
즉 다시말해...
- memberService 빈은 memberRepository()를 호출한다 -> new MemoryMemberRepository()를 호출한다.
- orderService 빈은 memberRepository()를 호출한다 -> new MemoryMemberRepository()를 호출한다.
그렇다면 이는 여러개의 MemoryMemberRepository 객체가 생성되는 것으로 생각할 수 있다. 결국 싱글톤이 깨지는 것 처럼 보인다. 스프링 컨테이너는 이를 어떤식으로 해결할까? 테스트 코드를 보자.
테스트 코드
package hello.core.singleton;
public class ConfigurationSingletonTest {
@Test
void ConfigurationTest(){
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService =
ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService =
ac.getBean("orderService", OrderServiceImpl.class);
MemoryMemberRepository memoryMemberRepository =
ac.getBean("memberRepository", MemoryMemberRepository.class);
....
}
}
아마 위 코드를 실행하면 결과는 이런식이 되지 않을까?
위 코드의 추측한 결과
//memberService 생성과정에서 출력
call AppConfig.memberService
call AppConfig.memberRepository
//orderService 생성과정에서 출력
call AppConfig.orderService
call AppConfig.memberRepository
//memoryMemberService 생성과정에서 출력
call AppConfig.memberRepository
실제결과는 다음과 같다.
실제 결과
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.orderService
원래 예상대로라면 "call AppConfig.memberRepository"가 세번 호출되었어야 하는데 반해 실제로는 한번만 호출이 되었다.
@Configuration과 바이트 조작코드
@Test
void configurationDeep(){
ApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean.getClass() = " + bean.getClass());
}
bean.getClass() = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$641ec7cb
출력 결과를 보면 class hello.core.AppConfig 뒤에 $$EnhancerBySpringCGLIB$$641ec7cb와 같이 이상한게 적혀있는 것을 볼 수 있다. 만일 bean이 내가만든 클래스라면 "class hello.core.AppConfig"가 나와야한다. 그런데 뒤에 이상한 것들이 붙어있는 것으로 보아 이는 내가 만든 클래스가 아니라는 것을 알 수 있다.
이것은 사실 내가 만든 클래스가 아니라 스프링이 CGLIB라는 바이트 코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 클래스를 스프링 빈으로 등록한 것이다.
그리고 스프링 빈으로 등록된 임의의 다른 클래스는 싱글톤이 보장되도록 해준다.
아까전에 "call AppConfig.memberRepository"가 한번만 출력된 것이 이 때문이라는 것이다.
AppConfig@CGLIB는 아마 이런식으로 등록되어 있을 것이다.
@Bean
public MemberRepository memberRepository(){
//싱글톤을 보장해주는 if else
if(스프링에 memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있다면){
return 스프링 컨테이너에서 찾아서 반환;
}
else{
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 생성한 MemberRepository 객체;
}
}
결과적으로 @Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다.
@Configuration을 사용하면 CGLIB를 사용해서 싱글톤을 보장하는 것이다.
@Configuration을 붙이지 않는 경우
그렇다면 @Configuration을 사용하지 않는다면 어떨까? AppConfig에서 @Configuration만 제거하고 테스트 코드를 재실행 해보자.
@Configruation을 제거한 AppConfig
//@Configuration
public class AppConfig{
....
}
테스트 코드 결과
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.memberRepository
call AppConfig.orderService
call AppConfig.memberRepository
bean.getClass() = class hello.core.AppConfig
확실히 @Configuration을 제거하기 전과는 다름을 볼 수 있다. 우리가 기존에 추측했던 것처럼 객체들이 new(싱글톤 깨짐)되고 있고 또한 스프링 빈에 등록된 클래스또한 AppConfig@CGLIB가 아니라 AppConfig이다.
정리
- @Bean만 사용해도 빈에 등록은 되지만, 싱글톤은 보장되지 않는다.
- memberRepository() 처럼 의존관계 주입이 필요해서 메소드를 직접 호출할 때 싱글톤을 보장하지 않는다.
- 스프링 설정정보(xxxConfig)는 항상 @Configuration을 사용하자.
'Spring' 카테고리의 다른 글
Spring- 컴포넌트 스캔시 필터 (0) | 2023.01.26 |
---|---|
Spring- 컴포넌트 스캔과 자동 의존관계 주입 (0) | 2023.01.26 |
Spring- 싱글톤 (0) | 2023.01.24 |
Spring- 스프링 빈 메타 정보 설정(BeanDefinition) (0) | 2023.01.19 |
Spring- BeanFactory와 ApplicationContext (0) | 2023.01.19 |