스프링 컨테이너는 싱글톤 패턴의 문제를 해결하면서, 객체 인스턴스를 싱글톤(1개만 생성)으로 관리한다.
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer(){
//AppConfig appConfig = new AppConfig();
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//조회
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
//참조값이 같은 것을 확인
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
assertThat(memberService1).isSameAs(memberService2);
}
지역변수, 파라미터, ThreadLocal등을 사용해야 한다.ThreadLocal이란?
자바의 클래스이고 오직한 쓰레드에 의해서 읽고 쓰여질 수 있는 변수이다. ThreadLocal 변수를 선언하면멀티쓰레드 환경에서 각 쓰레드마다독립적인 변수를 가지고 접근할 수 있다.
예를 들어, 일반 변수의 수명은 특정 코드 블록 범위 내에서만 유효하지만, 쓰레드는 쓰레드 영역에 변수를 설정할 수 있기 때문에, 특정 쓰레드가 실행하는 모든 코드에서 그 쓰레드에 설정된 변수 값을 사용할 수 있게 된다.
package com.example.demo.singleton;
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 int getPrice(){
return price;
}
}
package com.example.demo.singleton;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
class StatefulServiceTest {
@Test
void statfulServiceSingleton(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//ThreadA: A 사용자 10000원 주문
int userAPrice =statefulService1.order("userA", 10000);
int userBPrice = statefulService2.order("userB", 20000);
//ThreadA: 사용자A 주문 금액 조회
int price = statefulService1.getPrice();
System.out.println("price = " + userAPrice);
assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
static class TestConfig{
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
StatefulService 코드에 order 함수의 반환 타입이 void형이었다면get.Price()를 할 때 치명적인 오류가 발생할 수 있다. statefulService1, statefulService2는 order를 같이 사용하기 때문에 price의 값이 A가 요청했 을 때 10000원이었다가 B가 요청할 때 20000원으로 바뀌면 나중에 A의 금액을 조회할 때 20000원이order 함수의 반환 타입을 int로 하여 price값을 저장할 수 있게 해준다. 나중에 조회할 때는 A의 값 B의 값 따로 조회할 수 있게 설정사용자 A 이후에 사용자 B가 왔기 때문에 getPrice()를 하게 되면 20000이 나오게 된다.memberService, memberRepository, orderService가 여러번 호출되어야 하지만, 한 번씩밖에 호출이 안되었다.public class ConfigurationSingletonTest {
@Test
void configurationTest(){
AnnotationConfigApplicationContext 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 memberRepositoy1 = memberService.getMemberRepositoy();
MemberRepository memberRepositoy2 = memberService.getMemberRepositoy();
System.out.println("memberService -> memberRepository = " + memberRepositoy1);
System.out.println("memberService -> memberRepository = " + memberRepositoy2);
System.out.println("memberRepository = " + memberRepository);
Assertions.assertThat(memberService.getMemberRepositoy()).isSameAs((memberRepository));
Assertions.assertThat(orderService.getMemberRepository()).isSameAs((memberRepository));
}
ac.getBean(AppConfig.class); 를 직접 호출해주면 이상한 게 나온다bean = class com.example.demo.AppConfig$$SpringCGLIB$$0class.com.example.demo.AppConfig@Test
void configurationDeep(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
AppConfig를 가져다가 CGLIB라는 라이브러리를 상속받아서 다른 클래스를 만든 것AppConfig가 실행된다고 알고 있지만, 사실은AppConfig@CGLIB가 실행되고 있는 것이다.AppConfig는 아마 내부적으로 이렇게 실행될 것이다.
@Bean
public MemberRepository memberRepository(){
if(MemoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?){
return 스프링 컨테이너에서 찾아서 반환;
} else {
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환
}
}
memberService가 참조하는 memberRepository의 주소가 서로 달라진다.@AutoWired를 통해 직접 의존성 주입을 해주는 방법이 있지만 잘 사용할 일이 없으므로 설명은 생략하겠다.