수동 빈 등록이 싱글톤 보장될 수 있는 이유
사실, 스프링에서 @Bean 애노테이션을 사용하여 수동 등록을 해도 싱글톤으로 등록된다.
하지만, 문제는 내부의 의존관계를 메서드 호출을 통해서 주입해주는 경우 발생하게 된다.
이러한 경우에는 객체가 새로 생성되기 때문에 DI컨테이너 내에 싱글톤이 깨지게 된다.
이 문제를 해결해주기 위한 애노테이션이 바로 @Configuration인 것이다.
@Bean을 사용하여 수동으로 빈을 등록할 때, @Configuration을 적어줌으로써 싱글톤이 유지되게 된다.
@Configuration의 유무에 따른 싱글톤 보장 여부
예제코드와 함께 직관적으로 살펴보면 아래와 같다.
- ItemService와 ItemRepository 클래스.
public class ItemRepository { }
@Getter
@RequiredArgsConstructor
public class ItemService{
private final ItemRepository itemRepository;
}
- 빈 등록 & 의존성 주입을 위한 설정파일 (AppConfig 파일)
public class AppConfig{
@Bean
public ItemRepository itemRepository() {
return new ItemRepository();
}
@Bean
public ItemService itemService1() {
return new ItemService(itemRepository());
}
@Bean
public ItemService itemService2() {
return new ItemService(itemRepository());
}
}
- 위와 같은 두 개의 파일이 있고, 아래와 같이 테스트코드를 작성한다면 어떻게 될까?
@Test
void SingletonTest() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ItemRepository itemRepository = ac.getBean(ItemRepository.class);
ItemService itemService1 = ac.getBean( "itemService1", ItemService.class);
ItemService itemService2 = ac.getBean( "itemService2", ItemService.class);
assertThat(itemService1.getItemRepository()).isSameAs(itemRepository);
assertThat(itemService2.getItemRepository()).isSameAs(itemRepository);
}
- 서로 다른 빈인 itemService1과 itemService2를 불러온다.
- ItemService클래스 내에서 ItemRepository 객체를 생성하고 반환해 주는데,
만약 싱글톤이 보장된다면 "itemService1 빈과 itemService2 빈의 getItemRepository()의 결과"가 "itemRepository"와
같은 빈이여야 할 것이다.
- 위 테스트의 실행 결과는 Fail이다.
- 당연하게도 그 이유는 itemService1이나 itemService2 빈 생성 시에, ItemRepository 객체를 새로 생성해서 의존관계를 주입 하기 때문이다.
이 문제를 해결하기 위한 것이 바로 @Configuration 애노테이션이다.
위 AppConfig 파일에 아래와 같이 @Configuration애노테이션을 붙여보자.
@Configuration
public class AppConfig{
@Bean
public ItemRepository itemRepository() {
return new ItemRepository();
}
@Bean
public ItemService itemService1() {
return new ItemService(itemRepository());
}
@Bean
public ItemService itemService2() {
return new ItemService(itemRepository());
}
}
- 그러고 나서, 동일한 테스트 코드를 실행하면 Pass가 뜰 것이다.
- 즉 itemService1과 itemService2 모두 동일한 ItemRepository를 참조하는 것이라고 볼 수 있다.
@Configuration의 CGLIB 프록싱
애노테이션 하나 붙였다고 싱글톤이 바로 적용되는 이유는 바로 AppConfig파일에 @Configuration을 적용시킴으로써
AppConfig@CGLIB이라는 가짜 프록시 객체를 빈으로 등록하기 때문이다.
실제로 @Configuration의 유무에 따라서 itemRepository라는 스프링 빈을 조회해 보면 아래와 같다.
- @Configuration 미적용 상태.
- @Configuration 적용 상태
위와 같이 @Configuration 유무에 따라 등록되는 스프링 빈이 달라지게 된다.
@Configuration를 적용하면 스프링이 CGLIB이라는 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 클래스를 만들고, 등록하는 것이다.
그래서, 이후에 AppConfig를 상속받아 만들어진 AppConfig@CGLIB에서 @Bean이 붙은 메서드가 컨테이너에 존재하는지 확인하고, 등록 혹은 생성 후 등록을 통해서 싱글톤이 보장되게끔 하는 것이다.
CGLIB 프록싱의 규칙
사실 이 포스팅을 하게 된 이유이기도 하다.
크게 두 가지 규칙을 기억하면 된다.
1. CGLIB 프록싱은 결국 상속을 통해서 이루어진다. 즉, 상속 규칙을 따라야한다.
- @Configuration이 적용되는 클래스는 final이면 안된다.
- @Bean이 적용되는 메소드는 final이나 private이면 안된다.
너무나도 당연하다. 자바는 private이나 final은 상속을 지원하지 않기 때문이다.
2. static @Bean 메소드는 당연히 오버라이딩이 불가하기 때문에, CGLIB 프록싱이 적용되지 않는다.
'backend > Spring' 카테고리의 다른 글
빈번한 북마크 update요청을 방지하기 위한 사용자별 스레드 관리 (0) | 2023.09.13 |
---|---|
무한 스크롤 구현 및 성능개선 (Pagination) (0) | 2023.09.13 |
Fetch join과 Paging (0) | 2023.08.14 |
[Spring] 스프링의 DI를 이용한 전략패턴 도입 (0) | 2023.06.08 |
[JPA] 동일 트랜잭션 내 OneToMany 필드 객체의 데이터와 DB 데이터의 불일치 (0) | 2023.04.21 |