참고
Spring core basic 시리즈는 김영한 님의 "스프링 핵심 원리 - 기본편" 강의를 정리한 글입니다. 글에 첨부된 사진은 해당 강의의 강의 자료에서 캡쳐한 것입니다. 제 Github에만 올려뒀다가, 정보 공유와 강의 홍보(?)를 위해 블로그에도 업로드합니다. 마크다운을 잘 쓰지 못해서 가독성이 조금 떨어지는 점 양해 바랍니다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
@Configuration과 싱글톤
AppConfig.java
//package, import 생략
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository()); //
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
AppConfig를 살펴보면 뭔가 이상한 점이 있다.
분명히 스프링 컨테이너는 싱글톤 패턴으로 객체를 관리한다고 했는데, AppConfig의 코드만 보면 memberService()
와 orderService()
에서 MemoryMemberRepository의 객체가 각각 한 번씩, 총 두번 생성되는 것처럼 보인다.
결과적으로 각각 다른 2개의 MemoryMemberRepository가 생성되면서 싱글톤이 깨지는 것 처럼 보인다. 스프링이 아무리 엄청난 프레임워크라고 해도, @Bean
annotation이 붙어 있는 코드가 저렇게 쓰여져 있기 때문에 저 코드를 그대로 실행시킬 수 밖에 없다. 프레임워크지 마법은 아니니까...
한번 테스트 코드로 MemoryMemberRepository의 객체가 1개인지 2개인지 확인해보자.
MemoryMemberRepository 객체를 주입받는 MemberServiceImpl과 OrderServiceImpl에 테스트용 코드를 임시로 추가해보자.
MemberServiceImpl.java
package com.kloong.corebasic1.member;
public class MemberServiceImpl implements MemberService{
//DIP 만족. 인터페이스에만 의존한다. 어떤 구현체가 들어올지 모른다.
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
//로직 코드 생략
//테스트 용도 코드
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
OrderServiceImpl.java
package com.kloong.corebasic1.order;
//import 생략
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
//로직 코드 생략
//테스트 용도 코드
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
이제 테스트 코드로 확인을 해보자.
ConfigurationSingletonTest.java
//package, import 생략
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("memberRepository = " + memberRepository);
System.out.println(
"memberService -> memberRepository = " + memberRepository1);
System.out.println(
"orderService -> memberRepository = " + memberRepository2);
Assertions.assertThat(memberRepository).isSameAs(memberRepository1);
Assertions.assertThat(memberRepository).isSameAs(memberRepository2);
Assertions.assertThat(memberRepository1).isSameAs(memberRepository2);
}
}
테스트를 해보니, MemberServiceImpl이 주입 받은 MemberRepository 객체와 OrderServiceImpl이 주입 받은 MemberRepository 객체가 서로 동일하다!
분명 AppConfig 코드를 보면 new MemoryMemberRepository()
코드가 2번 실행되는 것처럼 보이는데 어떻게 된 일일까?
스프링이 AppConfig의 메소드 중 일부를 호출을 하지 않는 것일까? 로그를 찍어서 확인해보자.
AppConfig.java
//package, import 생략
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
@Bean
이 붙어있는 메소드의 실행 순서는 보장되지 않지만, 아무튼 스프링 컨테이너가 @Bean
메소드를 전부 한 번씩은 실행시키기 때문에 위 코드에 의하면 "call AppConfig.memberRepository" 로그가 적어도 3번은 출력될 것이라고 예측할 수 있다.
그런데 짜잔!
위에서 작성해 둔 ConfigurationSingletonTest 코드를 실행시켜보면 "call AppConfig.memberRepository" 로그가 단 한 번만 출력되는 것을 확인할 수 있다.
"call AppConfig.memberRepository", "call AppConfig.memberService", "call AppConfig.orderService" 이 3개의 로그가 각각 한 번씩만 출력된다.
스프링이 어떻게 하는지는 잘 모르겠지만, 아무튼 싱글톤을 보장해준다는 사실을 확인할 수 있다. 어떻게 하는지는 다음 시간에 계속...
'Spring > Spring core basic' 카테고리의 다른 글
[Spring core basic] 28 - 컴포넌트 스캔과 의존관계 자동 주입 (0) | 2022.05.02 |
---|---|
[Spring core basic] 27 - @Configuration과 바이트코드 조작 (0) | 2022.05.02 |
[Spring core basic] 25 - 싱글톤 패턴 주의점 (0) | 2022.05.02 |
[Spring core basic] 24 - 싱글톤 컨테이너 (0) | 2022.05.02 |
[Spring core basic] 23 - 웹 애플리케이션과 싱글톤 패턴 (0) | 2022.05.02 |
댓글