๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
โญ Personal_Study/Spring

Singleton Container

by ํฌ์ŠคํŠธ์‰์ดํฌ 2023. 1. 6.

Singleton Container

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ & ์‹ฑ๊ธ€ํ†ค

์ผ๋ฐ˜์ ์€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์—ฌ๋Ÿฌ ๊ณ ๊ฐ์ด ๋™์‹œ์— ์š”์ฒญ์„ ํ•œ๋‹ค!

์Šคํ”„๋ง ์—†๋Š” ์ˆœ์ˆ˜ํ•œ DI์ปจํ…Œ์ด๋„ˆ

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);

        Assertions.assertThat(memberService1).isNotSameAs(memberService2);
    }
}

์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ๋Š” ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ๋œ๋‹ค -> ๋ฉ”๋ชจ๋ฆฌ ๋‚ญ๋น„!

๋”ฐ๋ผ์„œ ์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ์‹์ด ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด!

โœ” 1๊ฐœ์˜ ๊ฐ์ฒด๋งŒ ์ƒ์„ฑ๋˜๊ณ  ๊ณต์œ ํ•˜๋„๋ก ์„ค๊ณ„

์‹ฑ๊ธ€ํ†ค ํŒจํ„ด

โœ” ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด(Singleton Pattern): ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋”ฑ 1๊ฐœ๋งŒ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•˜๋Š” ๋””์ž์ธ ํŒจํ„ด

private ์ƒ์„ฑ์ž๋ฅผ ์ด์šฉํ•ด ์™ธ๋ถ€์—์„œ ์ž„์˜๋กœ new ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๋‘ ๊ฐœ ์ด์ƒ์˜ ๊ฐ์ฒด ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•œ๋‹ค

์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์„ ์ ์šฉํ•œ ์ฝ”๋“œ

package hello.core.singleton;

public class SingletonService {

    // 1. static ์˜์—ญ์— 1๊ฐœ์˜ ๊ฐ์ฒด๋งŒ์„ ์ƒ์„ฑ
    private static final SingletonService instance = new SingletonService();

    // 2. ๊ฐ์ฒด ์ธ์Šคํ„ด์Šค๊ฐ€ ํ•„์š”ํ•˜๋ฉด ํ•ด๋‹น public static ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์กฐํšŒ ๊ฐ€๋Šฅ
    public static SingletonService getInstance() {
        return instance;
    }

    // 3. private ์ƒ์„ฑ์ž
    private SingletonService() {

    }

    public void logic() {
        System.out.println("์‹ฑ๊ธ€ํ†ค ๊ฐ์ฒด ๋กœ์ง ํ˜ธ์ถœ");
    }
}
  1. static ์˜์—ญ์— ํ•œ ๊ฐœ์˜ ๊ฐ์ฒด instance๋งŒ์„ ์ƒ์„ฑํ•ด์„œ ์˜ฌ๋ ค๋‘”๋‹ค.
  2. ํ•ด๋‹น ๊ฐ์ฒด ์ธ์Šคํ„ด์Šค๊ฐ€ ํ•„์š”ํ•˜๋ฉด ์˜ค์ง getInstance() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์กฐํšŒ ๊ฐ€๋Šฅ(ํ•ญ์ƒ ๊ฐ™์€ ์ธ์Šคํ„ด์Šค ๋ฐ˜ํ™˜)
  3. ์ƒ์„ฑ์ž๋ฅผ private์œผ๋กœ ๋ง‰์•„ ์™ธ๋ถ€์—์„œ ์ƒˆ๋กœ์šด ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐฉ์ง€

์‹ฑ๊ธ€ํ†ค ํŒจํ„ด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ


@Test
@DisplayName("์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์„ ์ ์šฉํ•œ ๊ฐ์ฒด ์‚ฌ์šฉ")
void singletonServiceTest() {

    SingletonService singletonService1 = SingletonService.getInstance();
    SingletonService singletonService2 = SingletonService.getInstance();

    //์ฐธ์กฐ๊ฐ’์ด ๊ฐ™์€ ๊ฒƒ์„ ํ™•์ธ
    System.out.println("singletonService1 = " + singletonService1);
    System.out.println("singletonService2 = " + singletonService2);

    // same: ๊ฐ์ฒด ๋น„๊ต (==)
    // equal: equals ๋ฉ”์„œ๋“œ
    Assertions.assertThat(singletonService1).isSameAs(singletonService2);
}

๊ฐ™์€ ๊ฐ์ฒด ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค.

์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์˜ ๋‹จ์ 

์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์„ ๊ตฌํ˜„ํ•˜๋Š” ์ฝ”๋“œ ์ž์ฒด๊ฐ€ ๋งŽ๋‹ค

ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ตฌ์ฒด ํด๋ž˜์Šค์— ์˜์กดํ•˜๊ฒŒ ๋œ๋‹ค (DIP, OCP ์œ„๋ฐ˜)

ํ…Œ์ŠคํŠธํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์›€

๋‚ด๋ถ€ ์†์„ฑ ๋ณ€๊ฒฝ or ์ดˆ๊ธฐํ™” ์–ด๋ ค์›€

โœ”  ์œ ์—ฐ์„ฑ์ด ๋–จ์–ด์ง„๋‹ค

์‹ฑ๊ธ€ํ†ค ์ปจํ…Œ์ด๋„ˆ

์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์˜ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๋ฉด์„œ ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์‹ฑ๊ธ€ํ†ค ์ปจํ…Œ์ด๋„ˆ

โœ”  ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๋Š” ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์„ ์ ์šฉํ•˜์ง€ ์•Š์•„๋„ ๊ฐ์ฒด ์ธ์Šคํ„ด์Šค๋ฅผ ์•Œ์•„์„œ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ๊ด€๋ฆฌํ•œ๋‹ค

์‹ฑ๊ธ€ํ†ค ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ: ์‹ฑ๊ธ€ํ†ค ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ

์‹ฑ๊ธ€ํ†ค ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด

  • ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์„ ์œ„ํ•œ ์ฝ”๋“œ๋ฅผ ๋”ฐ๋กœ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค
  • DIP, OCP, ํ…Œ์ŠคํŠธ, private ์ƒ์„ฑ์ž ๋“ฑ ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆek.

์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

    @Test
    @DisplayName("์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์™€ ์‹ฑ๊ธ€ํ†ค")
    void springContainter() {

        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);

        Assertions.assertThat(memberService1).isSameAs(memberService2);
    }

์‹ฑ๊ธ€ํ†ค ์ปจํ…Œ์ด๋„ˆ ์ ์šฉ ํ›„

์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋ฅผ ๊ณต์œ ํ•ด์„œ ํšจ์œจ์ ์œผ๋กœ ์žฌ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ฐธ๊ณ : ์Šคํ”„๋ง์€ ์‹ฑ๊ธ€ํ†ค ๋ฐฉ์‹ ์™ธ์˜ ๋ฐฉ์‹์œผ๋กœ๋„ ๋นˆ์„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค (๋นˆ์Šค์ฝ”ํ”„)

์‹ฑ๊ธ€ํ†ค ๋ฐฉ์‹์˜ ์ฃผ์˜์ 

โœ” ์‹ฑ๊ธ€ํ†ค ๋ฐฉ์‹์€ ์—ฌ๋Ÿฌ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•˜๋‚˜์˜ ๋™์ผํ•œ ๊ฐ์ฒด ์ธ์Šคํ„ด์Šค๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒํƒœ๋ฅผ ์œ ์ง€(stateful)ํ•˜๊ฒŒ ์„ค๊ณ„ ํ•˜๋ฉด ์•ˆ๋œ๋‹ค.

โœ” ์‹ฑ๊ธ€ํ†ค์€ ๋ฌด์ƒํƒœ(Stateless)๋กœ ์„ค๊ณ„ํ•ด์•ผ ํ•œ๋‹ค

  • ํŠน์ • ํด๋ผ์ด์–ธํŠธ์— ์˜์กด์ ์ธ ํ•„๋“œ x
  • ํŠน์ • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ํ•„๋“œ x
  • (๊ฐ€๊ธ‰์ ) ์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅ
  • ํ•„๋“œ ๋Œ€์‹  ์ž๋ฐ”์—์„œ ๊ณต์œ ๋˜์ง€ ์•Š๋Š” ์ง€์—ญ๋ณ€์ˆ˜, ํŒŒ๋ผ๋ฏธํ„ฐ, ThreaLocal ๋“ฑ ์‚ฌ์šฉ

Stateful ์‹ฑ๊ธ€ํ†ค์˜ ๋ฌธ์ œ์ 

package hello.core.singleton;

public class StatefulService {

    private int price; // ์ƒํƒœ๋ฅผ ์œ ์ง€(Stateful)ํ•˜๋Š” ํ•„๋“œ

    public void order (String name, int price) {
        System.out.println("name = " + name + " price = " + price);
        this.price = price; // ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
    }

    public int getPrice() {
        return price;
    }
}
class StatefulServiceTest {

    @Test
    void statefulServiceSingleton() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);

        // Thread A: A์‚ฌ์šฉ์ž 10000์› ์ฃผ๋ฌธ
        statefulService1.order("userA", 10000);
        // Thread B: B์‚ฌ์šฉ์ž 20000์› ์ฃผ๋ฌธ
        statefulService2.order("userB", 20000);

        // Thread A: ์‚ฌ์šฉ์ž A ์ฃผ๋ฌธ ๊ธˆ์•ก ์กฐํšŒ
        int price = statefulService1.getPrice();
        System.out.println("price = " + price);

    }

    static class TestConfig {

        @Bean
        public StatefulService statefulService() {
            return new StatefulService();
        }
    }

}

price ํ•„๋“œ๊ฐ€ ๊ณต์œ ๋˜์–ด ๊ฐ’์ด ๋ฎ์–ด์”Œ์›Œ์ง„๋‹ค!

@Configuration๊ณผ ์‹ฑ๊ธ€ํ†ค

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), new FixDiscountPolicy());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}

memberService -> memberRepository()
orderService -> memberRepository()

์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ์—์„œ MemoryMemborRepository๋Š” ๋ช‡ ๋ฒˆ ํ˜ธ์ถœ๋  ๊ฒƒ์ธ๊ฐ€?

    @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);

        //๋ชจ๋‘ ๊ฐ™์€ ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐธ๊ณ ํ•˜๊ณ  ์žˆ๋‹ค.
        System.out.println("memberService -> memberRepository = " + memberService.getMemberRepository());
        System.out.println("orderService -> memberRepository = " + orderService.getMemberRepository());
        System.out.println("memberRepository = " + memberRepository);

        //๋ชจ๋‘ ๊ฐ™์€ ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐธ๊ณ ํ•˜๊ณ  ์žˆ๋‹ค.

        assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);

        assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
    }
// ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ

memberService -> memberRepository = hello.core.member.MemoryMemberRepository@56e8b606
orderService -> memberRepository = hello.core.member.MemoryMemberRepository@56e8b606
memberRepository = hello.core.member.MemoryMemberRepository@56e8b606

2๋ฒˆ์ผ ๊ฒƒ ๊ฐ™์ง€๋งŒ ์‹ค์ œ๋กœ ์กฐํšŒํ•ด๋ณด๋ฉด 1๋ฒˆ๋งŒ ํ˜ธ์ถœ๋œ๋‹ค(๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐ)

@Configuration๊ณผ ๋ฐ”์ดํŠธ์ฝ”๋“œ ์กฐ์ž‘์˜ ๋งˆ๋ฒ•

์œ„์™€ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ ์Šคํ”„๋ง์€ ํด๋ž˜์Šค์˜ ๋ฐ”์ดํŠธ์ฝ”๋“œ๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœ๋˜๋„๋ก ํ•œ๋‹ค

@Test
void configurationDeep() {
    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    //AppConfig๋„ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋ก๋œ๋‹ค.
    AppConfig bean = ac.getBean(AppConfig.class);

    System.out.println("bean = " + bean.getClass());
    //์ถœ๋ ฅ: bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$bd479d70
}

AppConfig$$EnhancerBySpringCGLIB$$bd479d70???

์Šคํ”„๋ง์ด CGLIB๋ผ๋Š” ๋ฐ”์ดํŠธ์ฝ”๋“œ ์กฐ์ž‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด์„œ AppConfigํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์€ ์ž„์˜์˜ ๋‹ค๋ฅธ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ณ , ๊ทธ ๋‹ค๋ฅธ ํด๋ž˜์Šค๋ฅผ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค

@Bean๋งŒ ์‚ฌ์šฉํ•ด๋„ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋ก๋˜์ง€๋งŒ, ์‹ฑ๊ธ€ํ†ค์„ ๋ณด์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค

โœ” ์Šคํ”„๋ง ์„ค์ • ์ •๋ณด๋Š” ํ•ญ์ƒ @Configuration ์‚ฌ์šฉ

๋Œ“๊ธ€