Spring / Single-ton Pattern, Single-ton Container
by rlaehddnd0422์ฑ๊ธํค ์ปจํ ์ด๋ - ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ์ฑ๊ธํค
์๋ฅผ๋ค์ด, ์ฌ๋ฌ ํด๋ผ์ด์ธํธ๊ฐ ๋์์ memberService๋ฅผ ์์ฒญํ๋ค๊ณ ํ ๋, ์์ฒญํ ๋๋ง๋ค ์๋ก์ด ๊ฐ์ฒด๊ฐ ์์ฑ๋๊ณ ์ฌ์ฉ์ด ๋๋๋ฉด ์๋ฉธ๋๋ค.
โถ๏ธ ๋ฉ๋ชจ๋ฆฌ ๋ญ๋น๊ฐ ์ฌํจ.
TestCode
public class SingletonTest {
@Test
@DisplayName("์คํ๋ง ์๋ ์์ํ DI Container")
void pureContainer()
{
AppConfig appConfig = new AppConfig();
MemberService memberService1 = appConfig.memberService();
MemberService memberService2 = appConfig.memberService();
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
assertThat(memberService1).isNotSameAs(memberService2);
}
}
Solution โท ํด๋น ๊ฐ์ฒด๋ฅผ ๋ฑ 1๊ฐ๋ง ์์ฑํ๊ณ , ํด๋ผ์ด์ธํธ๊ฐ ๊ณต์ ํ๋๋ก ์ค๊ณํ๋ฉด ๋๋ค
์ด๋ฅผ ์ฑ๊ธํค ํจํด์ด๋ผ๊ณ ํ๋ค.
์ฑ๊ธํค ํจํด
ํด๋์ค์ ์ธ์คํด์ค๊ฐ ๋ฑ 1๊ฐ๋ง ์์ฑ๋๋ ๊ฒ์ ๋ณด์ฅํ๋ ๋์์ธ ํจํด
package hello.core.singleton;
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance()
{
return instance;
}
// ์์ฑ์๋ฅผ private๋ก ์ ์ธํด์ ์ธ๋ถ์์ new keyword๋ฅผ ์ฌ์ฉํ ๊ฐ์ฒด ์์ฑ์ ๋ง์
private SingletonService(){}
public void logic()
{
System.out.println("์ฑ๊ธํค ๊ฐ์ฒด ๋ก์ง ํธ์ถ");
}
}
1. static์์ญ์ ๊ฐ์ฒด instance๋ฅผ ๋ฏธ๋ฆฌ ํ๋ ์์ฑํด์ ์ฌ๋ ค๋๋ค.
2. ์์ฑ์๋ private๋ก ์ธ๋ถ์์ฑ์ ๋ง์๋๊ณ
3. getinstance() ๋ฉ์๋๋ฅผ ํตํด static์์ญ์ ์์ฑํด๋ ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํ๊ฒ ํ๋ค.
TestCode
ํด๋น ๊ฐ์ฒด๋ฅผ getinstance()๋ฅผ ํตํด์๋ง ๋ง๋ค ์ ์๊ฒ ์ค์ ํ๊ณ , getinstance()๋ static level์ ์๋ ๋ฏธ๋ฆฌ ๋ง๋ค์ด๋ ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํ๊ธฐ ๋๋ฌธ์ ์๋ก ๋ค๋ฅธ ์ธ์คํด์ค๊ฐ ๋ฆฌํด๋๋ ๊ฒ์ ๋ง์์ค๋ค.
MemoryMemberRepository๋ฅผ ์ฑ๊ธํค ํจํด์ผ๋ก ๋ฐ๊ฟ๋ณด์.
์ด ์ ํฌ์คํ ์์ ์๋ก ๋ค๋ฅธ Service (OrderService, MemberService) ์์ ์๋ก ๋ค๋ฅธ MemoryMemberRepository๋ฅผ ์ฌ์ฉํ๋๋ฐ,
์ฑ๊ธํค ํจํด์ผ๋ก ์ค๊ณ๋ฅผ ํ๋ฉด ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
๋ค์๊ณผ ๊ฐ์ด static ์์ญ์ instance๋ฅผ ํ๋ ๋ง๋ค์ด๋๊ณ getinstance() ๋ฉ์๋๋ก ํด๋น instance๋ง ๋ฆฌํดํ๋๋ก ์ค์ ํด์ฃผ์๊ณ ,
๋ง์ง๋ง์ผ๋ก ์์ฑ์๋ฅผ private๋ก ์ค์ ํ์ฌ ์ธ๋ถ์์ new๋ฅผ ์ด์ฉํ ๊ฐ์ฒด ์์ฑ์ ๋ฐฉ์งํ๋ค.
OrderServiceTest ๋จ๊ณ์์ ๊ฐ ์๋น์ค์ MemoryMemberRepository์ ๊ฐ์ฒด ์ฃผ์๋ฅผ ํ๋ฆฐํธํ๋ TestCode๋ฅผ ๋ง๋ค์ด๋ณธ ๊ฒฐ๊ณผ ๋ ์๋น์ค๊ฐ ๊ฐ์ MemoryMemberRepository๋ฅผ ๊ณต์ ํ๋๋ก ์ค์ ์ด ๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค.
์ฑ๊ธํค ํจํด ๋ฌธ์ ์
1. ๊ตฌํํ๋ ์ฝ๋์์ฒด๊ฐ ๋ง์ด ๋ค์ด๊ฐ๋ค.
2. ์์กด๊ด๊ณ์ ํด๋ผ์ด์ธํ๊ฐ ๊ตฌ์ฒด ํด๋์ค์ ์์กดํ๊ธฐ ๋๋ฌธ์( ์์ ๊ฐ์ case์์๋ ๊ตฌ์ฒด์ getinstance()์ ์์กดํจ ) DIP๋ฅผ ์๋ฐํ๋ค.
3. ํด๋ผ์ด์ธํธ๊ฐ ๊ตฌ์ฒด ํด๋์ค์ ์์กดํด์ OCP ์์น์ ์๋ฐํ ๊ฐ๋ฅ์ฑ์ด ๋์
4. ํ ์คํธ๊ฐ ์ด๋ ต๋ค.
5. ๋ด๋ถ ์์ฑ์ ๋ณ๊ฒฝํ๊ฑฐ๋ ์ด๊ธฐํํ๊ธฐ ์ด๋ ต๋ค.
6. private ์์ฑ์๋ก ์์ ํด๋์ค๋ฅผ ๋ง๋ค๊ธฐ ์ด๋ ต๋ค.
7. ๊ฒฐ๋ก ์ ์ผ๋ก ์ ์ฐ์ฑ์ด ๋จ์ด์ง.
8. ๊ทธ๋์ ์ํฐํจํด์ด๋ผ๊ณ ๋ถ๋ฆฌ๊ธฐ๋ ํจ.
์ฑ๊ธํค ์ปจํ ์ด๋
์ฑ๊ธํค ์ปจํ ์ด๋๋ ์ฑ๊ธํค ํจํด์ ๋ฌธ์ ์ ์ ํด๊ฒฐํ๊ณ , ๊ฐ์ฒด ์ธ์คํด์ค๋ฅผ ์ฑ๊ธํค์ผ๋ก ๊ด๋ฆฌํ๋ค.
์์ ๋ฐฐ์ด ์คํ๋ง ์ปจํ ์ด๋๋ ์ฑ๊ธํค ์ปจํ ์ด๋๋ก, ์คํ๋ง ๋น์ด ์ฑ๊ธํค์ผ๋ก ๊ด๋ฆฌ๋๋ค.
- ์คํ๋ง ์ปจํ ์ด๋๋ ์ฑ๊ธํค ์ปจํ ์ด๋ ์ญํ ์ ํ๋ค. ์ด๋ ๊ฒ ์ฑ๊ธํค ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ๊ด๋ฆฌํ๋ ๊ธฐ๋ฅ์ ์ฑ๊ธํค ๋ ์ง์คํธ๋ฆฌ๋ผ ํ๋ค.
๋ฐ๋ผ์ ์คํ๋ง ์ปจํ ์ด๋๋ฅผ ์ฌ์ฉํ๋ฉด ์ฑ๊ธํค ํจํด ์ฝ๋๋ฅผ ๊ตฌํํ์ง ์์๋ ๋๊ณ ,
DIP, OCP, Test, private ์์ฑ์๋ก๋ถํฐ ์์ ๋ก์์ง๋ค๋ ์ฅ์ ์ด ์๋ค.
Testcode
@Test
@DisplayName("์คํ๋ง ์ปจํ
์ด๋์ ์ฑ๊ธํค")
void springContainer()
{
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService1 = ac.getBean(MemberService.class);
MemberService memberService2 = ac.getBean(MemberService.class);
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
assertThat(memberService1).isSameAs(memberService2);
}
์์ ๊ฐ์ด ์คํ๋ง ์ปจํ ์ด๋๋ฅผ ์ฌ์ฉํ๋ฉด ํธ์ถํ ๋ ๋ง๋ค ๊ฐ์ ์ธ์คํด์ค๋ฅผ ๋ฆฌํดํ๋ค.
์คํ๋ง ์ปจํ ์ด๋ ๋๋ถ์ ๊ณ ๊ฐ์ ์์ฒญ์ด ์ฌ ๋ ๋ง๋ค ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ, ์ด๋ฏธ ๋ง๋ค์ด์ง ๊ฐ์ฒด๋ฅผ ๊ณต์ ํด์ ํจ์จ์ ์ผ๋ก ์ฌ์ฌ์ฉํ ์ ์๋ค.
โ๏ธ ์คํ๋ง์ ๊ธฐ๋ณธ ๋น ๋ฑ๋ก ๋ฐฉ์์ ์ฑ๊ธํค์ด์ง๋ง, ์ฑ๊ธํค ๋ฐฉ์๋ง ์ง์ํ๋ ๊ฒ์ ์๋๋ค. ์์ฒญํ ๋ ๋ง๋ค ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์์ฑํด์ ๋ฐํํ๋ ๊ธฐ๋ฅ๋ ์ ๊ณตํ๋ค.
์ฑ๊ธํค ๋ฐฉ์์ ์ฃผ์์
์์๋ฅผ ์ฐ์ ์ดํด๋ณด์
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;
}
}
TestCode
class StatefulServiceTest {
static class TestConfig
{
@Bean
public StatefulService statefulService()
{
return new StatefulService();
}
}
@Test
void statefulServiceSingleton()
{
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = applicationContext.getBean(StatefulService.class);
StatefulService statefulService2 = applicationContext.getBean(StatefulService.class);
//Thread A : A์ฌ์ฉ์ 1000์ ์ฃผ๋ฌธ
statefulService1.order("userA",1000);
//Thread B : B์ฌ์ฉ์ 2000์ ์ฃผ๋ฌธ
statefulService2.order("userB",2000);
//Thread A : A์ฌ์ฉ์ ์ฃผ๋ฌธ ๊ธ์ก ์กฐํ
int price = statefulService1.getPrice();
System.out.println("price = " + price);
}
}
price
/ expected : 1000
/ result : 2000
ThreadA๊ฐ ์ฌ์ฉ์A ์ฝ๋๋ฅผ ํธ์ถํ๊ณ ThreadB๊ฐ ์ฌ์ฉ์B ์ฝ๋๋ฅผ ํธ์ถํ๋ค ๊ฐ์ ํ์.
- StatefulService ์ price ํ๋๋ ๊ณต์ ๋๋ ํ๋์ธ๋ฐ, ํน์ ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ์ ๋ณ๊ฒฝํ๋ค.
์ฌ์ฉ์A์ ์ฃผ๋ฌธ๊ธ์ก์ 1000์์ด ๋์ด์ผ ํ๋๋ฐ, 2000์์ด๋ผ๋ ๊ฒฐ๊ณผ๊ฐ ๋์๋ค. - ์ค๋ฌด์์ ์ด๋ฐ ๊ฒฝ์ฐ, ์ ๋ง ํด๊ฒฐํ๊ธฐ ์ด๋ ค์ด ํฐ ๋ฌธ์ ๋ค์ด ํฐ์ง๋ค.
๊ณต์ ํ๋๋ ์กฐ์ฌํด์ผ ํ๋ค. ๋ฐ๋ผ์ ์คํ๋ง ๋น์ ํญ์ ๋ฌด์ํ(stateless)๋ก ์ค๊ณํ์.
์ฃผ์์
์ฑ๊ธํค ํจํด์ด๋ , ์คํ๋ง ์ปจํ ์ด๋๋ , ๊ฐ์ฒด ์ธ์คํด์ค๋ฅผ ํ๋๋ง ์์ฑํด์ ๊ณต์ ํ๋ ์ฑ๊ธํค ๋ฐฉ์์ ์ฌ๋ฌ ํด๋ผ์ด์ธํธ๊ฐ ํ๋์ ๊ฐ์ ์ธ์คํด์ค๋ฅผ ๊ณต์ ํ๊ธฐ ๋๋ฌธ์ ์ฑ๊ธํค ์ธ์คํด์ค๋ ์์ ๊ฐ์ด ์ํ๋ฅผ ์ ์ง(stateful)ํ๊ฒ ์ค๊ณํ๋ฉด ์๋๋ค.
- ๋ฌด์ํ(stateless)๋ก ์ค๊ณํด์ผ ํ๋ค.
- ํน์ ํด๋ผ์ด์ธํธ์ ์์กด์ ์ธ ํ๋๊ฐ ์์ผ๋ฉด ์๋จ.
- ํน์ ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ ํ๋๊ฐ ์์ผ๋ฉด ์๋จ.
- ๊ฐ๊ธ์ ์ฝ๊ธฐ๋ง ๊ฐ๋ฅํด์ผ ํ๋ค.
- ํ๋ ๋์ ์ ์๋ฐ์์ ๊ณต์ ๋์ง ์๋ ์ง์ญ๋ณ์, ํ๋ผ๋ฏธํฐ, ThreadLocal ๋ฑ์ ์ฌ์ฉํด์ผ ํ๋ค.
- ์คํ๋ง ๋น์ ํ๋์ ๊ณต์ ๊ฐ์ ์ค์ ํ๋ฉด ์ ๋ง ํฐ ์ฅ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
์ฑ๊ธํค ์ปจํ ์ด๋์์์ MemoryMemberRepository
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImp(getMemberRepository());
}
@Bean
public MemberRepository getMemberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService()
{
return new OrderServiceImp(getMemberRepository(), getPolicy());
}
@Bean
public DiscountPolicy getPolicy() {
return new RateDiscountPolicy();
}
}
์ฑ๊ธํค ์ปจํ ์ด๋์์๋ OrderService์ MemberService์์์ MemoryMemberRepository๊ฐ ์ฑ๊ธํค์ ์ ์งํ ์ ์์๊น?
์ฝ๋๋ง ๋ดค์๋๋ ๊ฐ๊ฐ new๋ก ์๋ก์ด ์ธ์คํด์ค๋ฅผ ๋ฆฌํดํด์ ์ฑ๊ธํค์ด ๊นจ์ง ๊ฒ ์ฒ๋ผ ๋ณด์ด์ง๋ง,
ํ ์คํธ๋ฅผ ํด๋ณด๋ฉด
ํ์ธํด๋ณด๋ฉด memberRepository ์ธ์คํด์ค๋ ๋ชจ๋ ๊ฐ์ ์ธ์คํด์ค๊ฐ ๊ณต์ ๋์ด ์ฌ์ฉ๋๋ค.
์ฑ๊ธํค์ด ์ ๊นจ์ง๋ค.. ์?
๋ ๋ฒ ํธ์ถ์ด ์๋๋ ๊ฑด๊ฐ??? ํธ์ถ๋ก๊ทธ๋ฅผ ๋จ๊ฒจ์ ์ดํด๋ณด์.
package hello.core;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImp;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImp(getMemberRepository());
}
@Bean
public MemberRepository getMemberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService()
{
System.out.println("call AppConfig.orderService");
return new OrderServiceImp(getMemberRepository(), getPolicy());
}
@Bean
public DiscountPolicy getPolicy() {
return new RateDiscountPolicy();
}
}
1. ์คํ๋ง ์ปจํ ์ด๋๊ฐ @Bean์ ํธ์ถํด์ ์คํ๋ง ๋น์ ์์ฑํ ๋ ํ ๋ฒ
2. orderService()๋ก์ง์์ ํ ๋ฒ
3. memberService()๋ก์ง์์ ํ ๋ฒ
getMemberRepository๊ฐ ์ด ์ธ ๋ฒ ํธ์ถ๋์ด์ผ ํ ๊ฒ ๊ฐ์ง๋ง ๋ฑ ํ ๋ฒ ํธ์ถํ๋ค.
๊ทธ๋์ AppConfig์ ์ด๋ ธํ ์ด์ @Configuration์ ๋นผ๊ณ ํ ์คํธ์ฝ๋๋ฅผ ์คํํด๋ณด์๋๋,
getmemberRepository()๊ฐ ์ด 3๋ฒ ํธ์ถ๋๋ฐ๋ค๊ฐ, ์ฑ๊ธํค๋ ๊นจ์ ธ๋ฒ๋ ธ๋ค.
์๋ง๋ @Configuration์ด ์ด ๋์๋ค์ ์ ์ดํ๋ ๊ฒ ๊ฐ๋ค.
@Configuration์ ๋ฐ์ดํธ์ฝ๋ ์กฐ์
์คํ๋ง์ ํด๋์ค์ ๋ฐ์ดํธ์ฝ๋๋ฅผ ์กฐ์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค.
@Test
void configurationDeep()
{
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
// -> bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$ad8aea39
}
์์ํ ํด๋์ค๋ผ๋ฉด class hello.core.AppConfig๋ก ์ถ๋ ฅ๋์ด์ผ ํ์ง๋ง,
ํด๋์ค๋ช ์ xxxCGLIB๊ฐ ๋ถ์ผ๋ฉด์ ๋ณต์กํด์ง ๊ฒ์ ๋ณผ ์ ์๋ค.
์ด๊ฒ์ ๋ด๊ฐ ๋ง๋ ํด๋์ค๊ฐ ์๋๋ผ ์คํ๋ง์ด CGLIB๋ผ๋ ๋ฐ์ดํธ์ฝ๋ ์กฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ AppConfig ํด๋์ค๋ฅผ ์์๋ฐ์ ์์์ ๋ค๋ฅธ ํด๋์ค๋ฅผ ๋ง๋ค๊ณ , ๊ทธ ๋ค๋ฅธ ํด๋์ค๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ ๊ฒ.
๊ทธ ์์์ ๋ค๋ฅธ ํด๋์ค๊ฐ ๋ฐ๋ก ์ฑ๊ธํค์ด ๋ณด์ฅ๋๋๋ก ํด์ค๋ค. ์๋ง๋ ๋ค์๊ณผ ๊ฐ์ด ๋ฐ์ดํธ ์ฝ๋๋ฅผ ์กฐ์ํด์ ์์ฑ๋์ด ์์ ๊ฒ์ด๋ค.(์ค์ ๋ก๋ CGLIB์ ๋ด๋ถ ๊ธฐ์ ์ ์ฌ์ฉํ๋๋ฐ ๋งค์ฐ ๋ณต์กํ๋ค.)
<์ ๋ฆฌ>
@Bean๋ง ์ฌ์ฉํด๋ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋์ง๋ง, ์ฑ๊ธํค์ ๋ณด์ฅํ์ง ์๋๋ค
์ฑ๊ธํค์ ๋ณด์ฅํ๊ธฐ ์ํด์ ์คํ๋ง ์ค์ ์ ๋ณด๋ ํญ์ @Configuration์ ์ฌ์ฉํ์!
<์ฐธ๊ณ ์๋ฃ>
'๐ Backend' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring / ์์กด๊ด๊ณ ์ฃผ์ - ์์ฑ์, ์์ ์, ํ๋, ๋ฉ์๋ (0) | 2023.02.03 |
---|---|
Spring / @ComponentScan, @Component (1) | 2023.02.02 |
Spring / Spring Container, Bean (0) | 2023.01.30 |
Spring / IoC, DI, Container (0) | 2023.01.30 |
Spring / AppConfig๋ฅผ ์ด์ฉํ ๊ธฐ์กด์ OCP, DIP ๋ฌธ์ ํด๊ฒฐ (1) | 2023.01.26 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
Study Repository
rlaehddnd0422