개요
- 스프링 컨테이너는 싱글톤 레지스트리
- 따라서 스프링 빈이 싱글톤이 되도록 보장해주어야 함
- 그런데 스프링이 자바 코드까지 조작하기는 어려움
- 그래서 스프링은 클래스의 바이트 코드를 조작하는 라이브러리를 사용
- 모든 비밀은 @Configuration을 적용한 AppConfig에 있음
확인 코드
package hello.core.singleton;
import hello.core.AppConfig;
import hello.core.member.MemberRepository;
import hello.core.member.MemberServiceImpl;
import hello.core.order.OrderServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
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);
MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
System.out.println("memberService -> memberRepository1 = " + memberRepository1);
System.out.println("orderService -> memberRepository2 = " + memberRepository2);
System.out.println("memberRepository = " + memberRepository);
Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
}
- 기존에 있던 ConfigurationSingletonTest 클래스에 configurationDeep()이라는 테스트를 추가
- bean의 클래스 정보를 확인해봄
- bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$96ac0018
- AppConfig 뒤에 특이한 내용이 포함됨
- 순수한 클래스라면 class hello.core.AppConfig가 출력되어야 함
- 스프링이 빈을 등록하는 과정에서 조작을 함
- 이것은 내가 만든 클래스가 아님
- 스프링이 CGLIB라는 바이트 코드 조작 라이브러리를 사용해 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 클래스를 스프링 빈으로 등록한 것임
그림
- 기존의 AppConfig를 상속받은 임의의 다른 클래스를 이용하여 싱글톤을 보장
AppConfig@CGLINB의 예상 코드
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
return 스프링 컨테이너에서 찾아서 반환;
} else { //스프링 컨테이너에 없으면
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환
}
}
- 아마 memberReposiroty() 메소드를 오버라이드 해서 이런 식으로 구성했을 것임
- 처음 실행했을 때는 else 문으로 분기되어 기존 로직(AppConfig의 memberRepository)을 호출
- 두번째부터는 오버라이드 된 메소드가 실행되어 스프링 컨테이너에서 빈을 찾아서 반환
- @Bean이 붙은 메소드마다 이미 스프링 빈이 존재하면 그 빈을 반환하고, 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어짐
- 이 때문에 싱글톤이 보장될 수 있음
- 참고: AppConfig@CGLIB는 AppConfig의 자식 타입이므로, AppConfig 타입으로 조회할 수 있음
- cf. 부모 타입으로 조회 시 자식 타입도 함께 조회됨
@Configuration을 적용하지 않고, @Bean만 적용할 때
- bean = class hello.core.AppConfig가 출력됨
- 순수한 AppConfig가 그대로 호출
- MemberRepository()가 3번 호출됨
- 1번은 @Bean에 의해 스프링 컨테이너에 등록하기 위함
- 2번은 각각 memberRepository()를 호출하면서 발생
- 세 개의 MemberRepository 인스턴스가 모두 달라 싱글톤이 보장되지 않음
- 스프링 컨테이너에서 관리되지도 않음
- 일반 자바 코드로 호출된 것이기 때문
- @Autowired를 사용하여 해결할 수도 있음. 이는 뒷 부분에서 학습
정리
- @Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않음
- memberRepository()처럼 의존관계 주입이 필요해서 메소드를 직접 홏루할 때 싱글톤을 보장하지 않음
- 크게 고민할 것이 없이 스프링 설정 정보는 항상 @Configuration을 사용
※ 본 게시글은 인프런의 스프링 핵심 원리 - 기본편(김영한)을 수강하고 정리한 내용입니다.
'Study > Spring' 카테고리의 다른 글
[스프링 핵심 원리 - 기본편] 싱글톤 컨테이너(@Configuration과 싱글톤) (0) | 2023.01.01 |
---|---|
[스프링 핵심 원리 - 기본편] 싱글톤 컨테이너(싱글톤 방식의 주의점) (0) | 2022.12.31 |
[스프링 핵심 원리 - 기본편] 싱글톤 컨테이너(싱글톤 컨테이너) (0) | 2022.12.31 |
[스프링 핵심 원리 - 기본편] 싱글톤 컨테이너(싱글톤 패턴) (0) | 2022.12.30 |
[스프링 핵심 원리 - 기본편] 싱글톤 컨테이너(웹 애플리케이션과 싱글톤) (0) | 2022.12.29 |