본문 바로가기
스프링/핵심 원리

[Spring] @Configuration과 싱글톤

by drCode 2022. 2. 15.
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
반응형

댓글