스프링 컨테이너를 통한 생성자 주입
스프링 컨테이너=@Configuration을 통하여 스프링의 빈들의 관계를 주입해보자
스프링을 적용안한 DI Container
즉
@Configuration과@Bean을 적용안했을때
public class AppConfig {
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}
스프링을 적용한 Spring Container
@Configuration
public class SpringConfig {
    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}
두개 다 의존관계 주입한다(Dependency Injection)
그렇다면 차이점은?
- 스프링 컨테이너는 싱글톤을 보장한다
싱글톤
스프링을 적용안한 DI 컨테이너를 테스트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SingletonTest {
	@Test
	@DisplayName("스프링 없는 순수한 DI 컨테이너")
	void pureContainer() {
	  AppConfig appConfig = new AppConfig();
    
    //1. 조회: 호출할 때 마다 객체를 생성
		MemberService memberService1 = appConfig.memberService();
		//2. 조회: 호출할 때 마다 객체를 생성
		MemberService memberService2 = appConfig.memberService();
		//참조값이 다른 것을 확인
		System.out.println("memberService1 = " + memberService1);
		System.out.println("memberService2 = " + memberService2);
    
		//memberService1 != memberService2
    assertThat(memberService1).isNotSameAs(memberService2);
  }
}

- 다른 인스턴스이다
- 즉 요청을 할 때마다 객체를 새로 생성
- 만약 고객 트래픽이 몰릴면 여러 객체가 생성되어 메모리 낭비가 심하다
- 이를 해결하기 위해 싱글톤 패턴 등장
스프링 컨테이너를 사용하는 테스트 코드
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
  ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
	//1. 조회: 호출할 때 마다 같은 객체를 반환
  MemberService memberService1 = ac.getBean("memberService", MemberService.class);
	//2. 조회: 호출할 때 마다 같은 객체를 반환
  MemberService memberService2 = ac.getBean("memberService", MemberService.class);
	//참조값이 같은 것을 확인
	System.out.println("memberService1 = " + memberService1); 
	System.out.println("memberService2 = " + memberService2);
	
	//memberService1 == memberService2
  assertThat(memberService1).isSameAs(memberService2);
}
 
싱글톤 주의점
여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 무상태로 설계해야 한다
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다
문제점 예시
문제가 있는 Bean(Stateful)
public class StatefulService {
	private int price; //상태를 유지하는 필드
	public void order(String name, int price) { 
		System.out.println("name = " + name + " price = " + price); 
		this.price = price; //여기가 문제!
	}
  public int getPrice() {
    return price;
	} 
}
테스트 코드
public class StatefulServiceTest {
	@Test
	void statefulServiceSingleton() {
          ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
          StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
          StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);
          //ThreadA: A사용자 10000원 주문 
          statefulService1.order("userA", 10000);
          //ThreadB: B사용자 20000원 주문 
          statefulService2.order("userB", 20000);
          //ThreadA: 사용자A 주문 금액 조회
          int price = statefulService1.getPrice();
          //ThreadA: 사용자A는 10000원을 기대했지만, 기대와 다르게 20000원 출력 System.out.println("price = " + price);
          Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
	}
	static class TestConfig {
	    @Bean
	    public StatefulService statefulService() {
	        return new StatefulService();
	    }
	} 
}
- price는 공유필드인데, 특정 클라이언트가 값을 변경한다
- Stateless유지하자
Stateless 테스트 코드
public class StatefulService {
	//private int price; //상태를 유지하는 필드
	public int order(String name, int price) { 
		System.out.println("name = " + name + " price = " + price); 
		//this.price = price;
		return price
	}
  
}
public class StatelessServiceTest {
	@Test
	void statefulServiceSingleton() {
	    ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
	    StatefulService statefulService1 = ac.getBean("statelessService", StatelessService.class);
	    StatefulService statefulService2 = ac.getBean("statelessService", StatelessService.class);
	
			//ThreadA: A사용자 10000원 주문 
			int userAPrice = statelessService1.order("userA", 10000);
			//ThreadB: B사용자 20000원 주문 
			int userBPrice = statelessService2.order("userB", 20000);
			
      //ThreadA: 사용자A 주문 금액 조회
			assertThat(userAPrice).isEqualTo(10000);
	}
	static class TestConfig {
	    @Bean
	    public StatelessService statelessService() {
	        return new StatelessService();
	    }
	} 
}
@Configuration
@Configuration이 있는 경우
@Configuration
public class AppConfig {
      @Bean
      public MemberService memberService() {
          System.out.println("call AppConfig.memberService");
          return new MemberServiceImpl(memberRepository());
      }
      @Bean
      public MemberRepository memberRepository() {
          System.out.println("call AppConfig.memberRepository");
          return new MemoryMemberRepository();
      }
      @Bean
      public OrderService orderService() {
          System.out.println("call AppConfig.orderService")
          return new OrderServiceImpl(
                  memberRepository(),
                  discountPolicy());
      }
      @Bean
      public DiscountPolicy discountPolicy() {
          return new RateDiscountPolicy();
      }
}
다음 순서대로 실행된다 가정 : memberService() → memberRepository()
- memberService()- System.out.println("call AppConfig.memberService");호출
- return new MemberServiceImpl(memberRepository());에서- memberRepository()호출
- memberRepository()로 이동 후,- System.out.println("call AppConfig.memberRepository");호출
- memberRepository()를 스프링 컨테이너에 스프링 빈으로 등록(이때 빈이름 :- memberRepository()-> 빈 객체:- MemoryMemberRepository@CGLIB로 등록)
- memberService()를 스프링 컨테이너에 의존관계 주입된 상태로 스프링 빈으로 등록(이때 빈이름 :- memberService()-> 빈 객체:- MemberServiceImpl@CGLIB로 등록
 
- memberRepository()→ 스프링 컨테이너에 이미 등록되어 있어서 건너뛴다
→ System.out.println("call AppConfig.memberRepository"); 한번만 호출된다(싱글톤을 보장해줌)
@Configuration이 없는 경우
//@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService() {
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        System.out.println("call AppConfig.memberRepository");
        return new MemoryMemberRepository();
    }
    @Bean
    public OrderService orderService() {
        System.out.println("call AppConfig.orderService")
        return new OrderServiceImpl(
                memberRepository(),
                discountPolicy()
              );
    }
    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}
위와 똑같이 순서대로 실행된다 가정 : memberService() → memberRepository()  → OrderService()
- memberService()- System.out.println("call AppConfig.memberService");호출
- return new MemberServiceImpl(memberRepository());에서- memberRepository()호출
- memberRepository()로 이동 후,- System.out.println("call AppConfig.memberRepository");호출
- 
        memberRepository()를 스프링 컨테이너에 스프링 빈으로 등록되지않고new MemoryMemberRepository()가 실행되어 주입관계를 넣는다즉, 단순히 DI만 한다 아래코드와 같다 @Bean public MemberService memberService() { System.out.println("call AppConfig.memberService"); return new MemberServiceImpl(new MemoryMemberRepository()); }
- memberService()를 스프링 컨테이너에 의존관계가 주입된 상태로 스프링 빈 등록하면서 객체 생성(이때 빈이름 :- memberService()-> 빈 객체:- MemberServiceImpl로 등록, 의존관계 주입
 
- memberRepository()- System.out.println("call AppConfig.memberRepository");호출
- memberRepository()를 스프링 컨테이너에 스프링 빈으로 등록(이때 빈이름 :- memberRepository()-> 빈 객체:- MemoryMemberRepository로 등록)
 
- OrderService()- System.out.println("call AppConfig.orderService")호출
- return new OrderServiceImpl(memberRepository(), discountPolicy());에서- memberRepository()호출
- memberRepository()로 이동 후,- System.out.println("call AppConfig.memberRepository");호출
- 다시 한번 new MemoryMemberRepository()실행
 
→ System.out.println("call AppConfig.memberRepository"); 3번 호출된다(싱글톤을 보장X)
해결방법
public class AppConfig {
    @Autowired MemberRepository memberRepository;
    @Bean
    public MemberService memberService() {
        System.out.println("call AppConfig.memberService");
        //return new MemberServiceImpl(memberRepository());
        return new MemberServiceImpl(memberRepository);
    }
    @Bean
    public MemberRepository memberRepository() {
        System.out.println("call AppConfig.memberRepository");
        return new MemoryMemberRepository();
    }
    @Bean
    public OrderService orderService() {
        System.out.println("call AppConfig.orderService")
        return new OrderServiceImpl(
                memberRepository,
                discountPolicy()
              );
    }
    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}
@Bean
    public MemberRepository memberRepository() {
				System.out.println("call AppConfig.memberRepository");
        return new MemoryMemberRepository();
    }
- 위 코드를 통하여 Bean으로 등록되고
- @Autowired MemberRepository memberRepository;를 통하여 다른 함수(- memberService(),- orderService())에서도 등록된 Bean을 사용하게 만든다
- 즉 전부 같은 하나의 Instance이다
걍 @Configuration을 붙이자
 
       
      
댓글 쓰기