728x90
반응형
AppConfig.java
@Configuration // : ==> 설정정보
public class AppConfig {
// @Bean memberService => new MemoryMemberRepository()
// @Bean orderService => new MemoryMemberRepository()
// Key : memberService , Value : new MemberServiceImpl(memberRepository())
@Bean // @Bean 을 붙여주면 스프링 컨테이너에 등록된다.
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository(); // DBRepository로 바뀔때 여기만 바꾸면 된다.
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository()
, discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy(); // RateDiscountPolicy로 바꿀때 여기만 바꾸면 된다.
}
}
- memberService 빈을 만드는 코드를 보면 'memberRepository()'를 호출한다
- 이 메서드를 호출하면 'new MemoryMemberRepository()'를 호출한다.
- orderService 빈을 만드는 코드도 동일하게 'memberRepository()'를 호출한다.
- 이 메서드를 호출하면 'new MemoryMemberRepository()'를 호출한다.
결과적으로 다른 2개의 'MemoryMemberRepository'가 생성되면서 싱글톤이 깨지는 것처럼 보인다.
스프링 컨테이너는 이 문제를 어떻게 해결할까?
MemberServiceImpl.java
package hello.core.member;
public class MemberServiceImpl implements MemberService{
// Ctrl + Shift + Enter : 세미콜론까지 완성
// private final MemberRepository memberRepository = new MemoryMemberRepository();
private final MemberRepository memberRepository;
// 인터페이스에 의존하는 memberRepository(추상화된) = 구체화된 호출
// 추상화 구체화 둘다 사용중이므로 좋은 코드가 아니다.
// 변경 시 문제가 된다. DIP를 위반하고 있는 코드이다.
// 지금은 추상화에만 의존하고 있다
// AppConfig에서 구체화 코드를 의존하게 했기 때문에 OCP와 DIP를 만족하는 코드이다.
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
// 테스트 용도
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
맨 밑에 getMemberRepository() 추가
OrderServiceImpl.java
package hello.core.order;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
public class OrderServiceImpl implements OrderService {
// private final MemberRepository memberRepository = new MemoryMemberRepository();
// private DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice); // SRD를 잘 지킨 형식
return new Order(memberId, itemName, itemPrice, discountPrice);
}
// 테스트 용도
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
맨 밑에 getMemberRepository() 추가
위의 경로에 ConfigurationSingletonTest 클래스 생성
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);
}
}
- 확인해보면 memberRepository 인스턴스는 모두 같은 인스턴스가 공유되어 사용된다.
- AppConfig의 자바 코드를 보면 분명히 각각 2번 'new MemoryMemberRepository' 호출해서 다른 인스턴스가 생성되어야 하는데?
- 어떻게 된 일일까? 혹시 두번 호출이 안되는 것일까? 실험을 통해 알아보자.
@Configuration // : ==> 설정정보
public class AppConfig {
// @Bean memberService => new MemoryMemberRepository()
// @Bean orderService => new MemoryMemberRepository()
// call AppConfig.memberService
// call AppConfig.memberRepository
// call AppConfig.memberRepository
// call AppConfig.orderService
// call AppConfig.memberRepository
// call AppConfig.memberService
// call AppConfig.memberRepository
// call AppConfig.orderService
// Key : memberService , Value : new MemberServiceImpl(memberRepository())
@Bean // @Bean 을 붙여주면 스프링 컨테이너에 등록된다.
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemoryMemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository(); // DBRepository로 바뀔때 여기만 바꾸면 된다.
}
@Bean
public OrderService orderService() {
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository()
, discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy(); // RateDiscountPolicy로 바꿀때 여기만 바꾸면 된다.
}
}
soutm 으로 call 문장들을 넣었다
위의 코드를 실행하면, 계산해볼때 5번의 call 호출이 일어나야 하지만 실제로 테스트를 해보면
3번밖에 나오지 않았다.
728x90
반응형
'스프링 > 핵심 원리' 카테고리의 다른 글
[Spring] 컴포넌트 스캔과 의존관계 자동 주입 시작하기 (0) | 2022.02.17 |
---|---|
[Spring] @Configuration과 바이트코드 조작의 마법 (0) | 2022.02.16 |
[Spring] 싱글톤 방식의 주의점 (0) | 2022.02.15 |
[Spring] 싱글톤 컨테이너 (0) | 2022.02.14 |
[Spring] 싱글톤 패턴 (0) | 2022.02.14 |
댓글