HJW's IT Blog

μŠ€ν”„λ§ 싱글톀 μ»¨ν…Œμ΄λ„ˆμ˜ λ™μž‘ 원리 λ³Έλ¬Έ

SPRING

μŠ€ν”„λ§ 싱글톀 μ»¨ν…Œμ΄λ„ˆμ˜ λ™μž‘ 원리

kiki1875 2024. 12. 2. 12:18

πŸ“” μ›Ή μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜κ³Ό 싱글톀

μŠ€ν”„λ§μ€ νƒœμƒμ΄ κΈ°μ—…μš© 온라인 μ„œλΉ„μŠ€ κΈ°μˆ μ„ μ§€μ›ν•˜κΈ° μœ„ν•΄ νƒ„μƒν–ˆλ‹€. 그렇기에 λŒ€λΆ€λΆ„μ˜ μŠ€ν”„λ§ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ€ μ›Ή μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ΄λ‹€. μ›Ή μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ νŠΉμ§•μ€, 보톡 μ—¬λŸ¬ 고객이 λ™μ‹œ μš”μ²­μ„ λ³΄λ‚Έλ‹€λŠ” 점이닀. κ·Έλ ‡λ‹€λ©΄ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ 컀지고, νŠΈλ ˆν”½μ΄ λ§Žμ•„μ§ˆ 수둝, 객체 생성 λΉ„μš©κ³Ό λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ€ μ¦κ°€ν•˜κ²Œ 되고, λ§Žμ€ λ¦¬μ†ŒμŠ€λ₯Ό μ†Œλͺ¨ν•œλ‹€λŠ” λœ»μ΄λ‹€. 

 

λ‹€μŒμ€, μŠ€ν”„λ§μ΄ μ—†λŠ” μˆœμˆ˜ν•œ DI μ»¨ν…Œμ΄λ„ˆ ν…ŒμŠ€νŠΈ μ˜ˆμ‹œμ΄λ‹€.

 

public class SingletonTest {
    
    @Test
    @DisplayName("μŠ€ν”„λ§ μ—†λŠ” 순수 DI μ»¨ν…Œμ΄λ„ˆ")
    void pureContainer(){

        AppConfig appConfig = new AppConfig();

        MemberService memberService1 = appConfig.memberService();
        
        MemberService memberService2 = appConfig.memberService();
        
        System.out.println("1 : " + memberService1);
        System.out.println("2 : " + memberService2);

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

 

κ²°κ³Όλ₯Ό 보면, 참쑰값도 λ‹€λ₯΄κ³ , isNotEqualTo ν…ŒμŠ€νŠΈλ„ ν†΅κ³Όλ˜λŠ” 것을 λ³Ό 수 μžˆλ‹€.

이 ν…ŒμŠ€νŠΈμ˜ μ˜λ―ΈλŠ”, μˆœμˆ˜ν•œ DI Container인 AppConfigλŠ” μš”μ²­λ§ˆλ‹€ 객체λ₯Ό μƒˆλ‘œ μƒμ„±ν•œλ‹€. λ¬Όλ‘  μš”μ²­ μˆ˜κ°€ 적닀면 상관 없을 것이닀. ν•˜μ§€λ§Œ, λ§Œμ•½ 고객 νŠΈλž˜ν”½μ΄ μ΄ˆλ‹Ή 100이라면, μ΄ˆλ‹Ή 100개의 객체가 μƒμ„±λ˜κ³  μ†Œλ©Έλ˜λŠ”λ°, λ©”λͺ¨λ¦¬ λ‚­λΉ„κ°€ 맀우 μ‹¬ν•˜λ‹€.

 

이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ 싱듀톀 νŒ¨ν„΄μ„ μ‚¬μš©ν•œλ‹€.

πŸ“”μ‹±κΈ€ν†€ νŒ¨ν„΄

싱글톀 νŒ¨ν„΄μ΄λž€, 클래슀의 μΈμŠ€ν„΄μŠ€κ°€ λ”± 1개만 μƒμ„±λ˜λŠ” 것을 보μž₯ν•˜λŠ” λ””μžμΈ νŒ¨ν„΄μ΄λ‹€. 그렇기에 객체 μΈμŠ€ν„΄μŠ€λ₯Ό 2개 이상 μƒμ„±ν•˜μ§€ λͺ»ν•˜λ„둝 막아야 ν•œλ‹€.

  • μƒμ„±μžλ₯Ό private 을 μ‚¬μš©ν•˜μ—¬, μ™ΈλΆ€μ—μ„œ μž„μ˜λ‘œ new ν‚€μ›Œλ“œλ‘œ 객체λ₯Ό μƒμ„±ν•˜λŠ”κ²ƒμ„ λ§‰λŠ”λ‹€.

λ‹€μŒμ€ 싱글톀 νŒ¨ν„΄μ˜ μ˜ˆμ‹œμ΄λ‹€.


public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance(){
        return instance;
    }

    private SingletonService(){
    }

    public void logic(){
        System.out.println("싱글톀 객체 호좜");
    }
}
  • static μ˜μ—­μ— 객체 instance λ₯Ό 미리 ν•˜λ‚˜ μƒμ„±ν•˜μ—¬ μ˜¬λ €λ‘”λ‹€
  • 이 객체 μΈμŠ€ν„΄μŠ€κ°€ ν•„μš”ν•˜λ©΄ 였직 getInstance() λ©”μ„œλ“œλ₯Ό ν†΅ν•΄μ„œλ§Œ 호좜이 κ°€λŠ₯ν•˜λ‹€
  • λ”± 1개의 객체 μΈμŠ€ν„΄μŠ€λ§Œ μ‘΄μž¬ν•΄μ•Ό ν•˜λ―€λ‘œ μƒμ„±μžλ₯Ό private 으둜 μ„€μ •ν•˜μ—¬ μ™ΈλΆ€μ—μ„œ new ν‚€μ›Œλ“œλ‘œμ˜ 접근을 막아야 ν•œλ‹€.

ν•œλ²ˆ 이 싱글톀이 μ§„μ§œ 싱글톀인지 확인해 보자. 이전 DI ν…ŒμŠ€νŠΈμ—μ„ , λ‘κ°œμ˜ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜λ©΄ 참쑰값이 μ„œλ‘œ λ‹¬λžλ˜ 것을 κΈ°μ–΅ν•˜μž.

 


    @Test
    @DisplayName("싱글톀 객체 ν…ŒμŠ€νŠΈ")
    void singletonServiceTest(){
        SingletonService singletonService1 = SingletonService.getInstance();
        SingletonService singletonService2 = SingletonService.getInstance();
        
        System.out.println("1 : " + singletonService1);
        System.out.println("2 : " + singletonService2);
        
        Assertions.assertThat(singletonService1).isSameAs(singletonService2);
        
        singletonService1.logic();
    }

 

κ²°κ³Ό

싱글톀 νŒ¨ν„΄μ˜ 문제점

싱글톀 νŒ¨ν„΄μ€ 객체λ₯Ό κ³΅μœ ν•˜μ—¬ 효율적으둜 μ‚¬μš©ν•  수 μžˆμ§€λ§Œ, μˆ˜λ§Žμ€ λ¬Έμ œμ λ“€μ„ κ°€μ§€κ³  μžˆλ‹€.

  • 싱글톀 νŒ¨ν„΄μ„ κ΅¬ν˜„ν•˜λŠ” μ½”λ“œ μžμ²΄κ°€ 많이 λ“€μ–΄κ°„λ‹€.
  • μ˜μ‘΄κ΄€κ³„μƒ ν΄λΌμ΄μ–ΈνŠΈκ°€ ꡬ체 ν΄λž˜μŠ€μ— μ˜μ‘΄ν•œλ‹€. → DIP μœ„λ°˜
  • ν΄λΌμ΄μ–ΈνŠΈκ°€ ꡬ체 ν΄λž˜μŠ€μ— μ˜μ‘΄ν•΄μ„œ OCP μ›μΉ™μ˜ μœ„λ°˜ν•  κ°€λŠ₯성이 λ†’λ‹€.
  • ν…ŒμŠ€νŠΈ ν•˜κΈ° μ–΄λ ΅λ‹€
  • λ‚΄λΆ€ 속성을 λ³€κ²½ν•˜κ±°λ‚˜ μ΄ˆκΈ°ν™” ν•˜κΈ° μ–΄λ ΅λ‹€
  • private μƒμ„±μžλ‘œ μžμ‹ μƒμ„±μžλ‘œ μžμ‹ 클래슀λ₯Ό λ§Œλ“€κΈ° μ–΄λ ΅λ‹€.
  • μœ μ—°μ„±μ΄ λ–¨μ–΄μ§„λ‹€

 

πŸ“” 싱글톀 μ»¨ν…Œμ΄λ„ˆ

Spring Container λŠ” 싱글톀 νŒ¨ν„΄μ˜ λ¬Έμ œμ μ„ ν•΄κ²°ν•˜λ©΄μ„œ, 객체 μΈμŠ€ν„΄μŠ€λ₯Ό μ‹±κΈ€ν†€μœΌλ‘œ κ΄€λ¦¬ν•œλ‹€. Spring Bean 은 기본적으둜 μ‹±κΈ€ν†€μœΌλ‘œ κ΄€λ¦¬λœλ‹€.

 

싱글톀 μ»¨ν…Œμ΄λ„ˆ

  • Spring Container λŠ” 싱글톀 νŒ¨ν„΄μ„ μ μš©ν•˜μ§€ μ•Šμ•„λ„ 객체 μΈμŠ€ν„΄μŠ€λ₯Ό μ‹±κΈ€ν†€μœΌλ‘œ κ΄€λ¦¬ν•œλ‹€.
  • Spring Container λŠ” 싱글톀 μ»¨ν…Œμ΄λ„ˆ 역할을 ν•œλ‹€. → 싱글톀 객체λ₯Ό μƒμ„±ν•˜κ³  κ΄€λ¦¬ν•˜λŠ” κΈ°λŠ₯을 Singleton Registry라 ν•œλ‹€.
  • Spring Container 의 이런 κΈ°λŠ₯ 덕에 μ‹±κΈ€ν†€μ˜ λͺ¨λ“  단점을 ν•΄κ²°ν•˜λ©΄μ„œ, 객체λ₯Ό μ‹±κΈ€ν†€μœΌλ‘œ μœ μ§€ν•  수 μžˆλ‹€.

 

    @Test
    @DisplayName("μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ™€ 싱글톀")
    void springContainer(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        MemberService memberService1 = ac.getBean("memberService", MemberService.class);
        MemberService memberService2 = ac.getBean("memberService", MemberService.class);

        System.out.println("1 : " + memberService1);
        System.out.println("2 : " + memberService2);

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

 

이전 ν…ŒμŠ€νŠΈλŠ” 직접 μž‘μ„±ν•œ 싱글톀 클래슀의 ν…ŒμŠ€νŠΈμ˜€λ‹€. 이번 ν…ŒμŠ€νŠΈλŠ” Spring Container 에 λ“±λ‘λ˜μ–΄ μ»¨ν…Œμ΄λ„ˆκ°€ bean 듀을 μ‹±κΈ€ν†€μœΌλ‘œ κ΄€λ¦¬ν•˜λŠ”μ§€ κ²€μ¦ν•˜κΈ° μœ„ν•œ ν…ŒμŠ€νŠΈμ΄λ‹€. 참쑰값도 κ°™κ³  ν…ŒμŠ€νŠΈλ„ ν†΅κ³Όν•œλ‹€.

 

싱글톀 μ»¨ν…Œμ΄λ„ˆ 적용 ν›„ λͺ¨μŠ΅

 

πŸ“”μ‹±κΈ€ν†€ λ°©μ‹μ˜ 주의점

싱글톀은 이전에 μ–ΈκΈ‰ν–ˆλ“―, μ—¬λŸ¬ μ‚¬μš©μžκ°€ κ³΅μœ ν•˜λŠ” 객체 μΈμŠ€ν„΄μŠ€μ΄λ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ—, 싱글톀 κ°μ²΄λŠ” μƒνƒœλ₯Ό μœ μ§€ν•΄μ„  μ•ˆλœλ‹€. (Stateless)

  • νŠΉμ • ν΄λΌμ΄μ–ΈνŠΈμ— 의쑴적인 ν•„λ“œκ°€ μžˆμ–΄μ„œλŠ” μ•ˆλœλ‹€
  • νŠΉμ • ν΄λΌμ΄μ–ΈνŠΈκ°€ 값을 λ³€κ²½ν•  수 μžˆλŠ” ν•„λ“œκ°€ μžˆμ–΄μ„  μ•ˆλœλ‹€.
  • 가급적 ReadOnly
  • ν•„λ“œ λŒ€μ‹ μ— μžλ°”μ—μ„œ κ³΅μœ λ˜μ§€ μ•ŠλŠ” μ§€μ—­λ³€μˆ˜, νŒŒλΌλ―Έν„°, ThreadLocal 등을 μ‚¬μš©ν•΄μ•Ό ν•œλ‹€

Stateful μ˜ˆμ‹œ

package hello.core.singleton;

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

        statefulService1.order("userA", 10000);
        statefulService2.order("userB", 20000);

        int price = statefulService1.getPrice();

        System.out.println(price);

        Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
    }

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

 

price λ₯Ό μ‘°νšŒν•˜λ©΄, 우리의 μ˜λ„λŒ€λ‘œλΌλ©΄ 10000원이어야 ν•œλ‹€. ν•˜μ§€λ§Œ, μ‹€μ œ μ‹€ν–‰ κ²°κ³ΌλŠ” 20000이 λ‚˜μ˜¨λ‹€.. → 같은 μΈμŠ€ν„΄μŠ€λ₯Ό κ³΅μœ ν•˜κΈ° λ•Œλ¬Έ.

 

κ·Έλž˜μ„œ μš°λ¦¬λŠ” λ‹€μŒκ³Ό 같이 섀계λ₯Ό ν•΄μ•Ό ν•œλ‹€

public class StatefulService {

    // private int price;
    public int order(String name, int price){
        System.out.println("name = " + name + " price = " + price  );
        return price;
    }

//    public int getPrice(){
//        return price;
//    }
}
    @Test
    void statefulServiceSingleton(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

        StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
        StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);

        int userA = statefulService1.order("userA", 10000);
        int userB = statefulService2.order("userB", 20000);


        System.out.println(userA);

        // Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
    }

 

@Configuration κ³Ό 싱글톀

이전에 μƒμ„±ν•œ AppConfig μ½”λ“œλ₯Ό ν•œλ²ˆ μ‚΄νŽ΄λ³΄μž


@Configuration
public class AppConfig {
 @Bean
 public MemberService memberService() {
	 return new MemberServiceImpl(memberRepository());
 }
 @Bean
 public OrderService orderService() {
	 return new OrderServiceImpl(
		 memberRepository(),
		 discountPolicy());
 }
 @Bean
 public MemberRepository memberRepository() {
	 return new MemoryMemberRepository();
 }
 ...
}
  • memberService μ—μ„œ ν˜ΈμΆœν•˜λŠ” memberRepository λŠ” new ν‚€μ›Œλ“œλ‘œ 생성이 되고, orderService μ—μ„œ ν˜ΈμΆœν•˜λŠ” memberRepository λ˜ν•œ new ν‚€μ›Œλ“œλ‘œ 생성이 λœλ‹€.
  • κ·Έλ ‡λ‹€λ©΄ 싱글톀이 κΉ¨μ§€λŠ” κ²ƒμΌκΉŒ?

κ²°λ‘ λΆ€ν„° λ§ν•˜μžλ©΄, 싱글톀은 κΉ¨μ§€μ§€ μ•ŠλŠ”λ‹€. ν…ŒμŠ€νŠΈλ‘œ 검증해 보자.

각 OrderServiceImpl κ³Ό MemberServiceImpl 에 memberRepository λ₯Ό λ°˜ν™˜ν•˜λŠ” μž„μ‹œ λ©”μ„œλ“œλ₯Ό λ§Œλ“  ν›„, ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μž.


    @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("1 : " + memberService.getMemberRepository());
        System.out.println("2 : " + orderService.getMemberRepository());
        System.out.println("3 : " + memberRepository);

    }

λͺ¨λ‘ 같은 참쑰값을 κ°€λ₯΄ν‚€κ³  μžˆλ‹€.

ν•œλ²ˆ AppConfig 에 둜그λ₯Ό 찍어 확인해 보자.


    @Bean
    public MemberService memberService(){
        System.out.println("Calling memberService");
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService(){
        System.out.println("Calling orderService");
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public MemberRepository memberRepository(){
        System.out.println("Calling memberRepository");
        return new MemoryMemberRepository();
    }

 

λΆ„λͺ… memberService μ—μ„œλ„, orderService μ—μ„œλ„ memberRepository λ©”μ„œλ“œλ₯Ό ν†΅ν•œ new ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•˜κΈ°μ—, “Calling memberRepository” κ°€ 3번 좜λ ₯λ˜μ–΄μ•Ό ν•  것 κ°™λ‹€. ν•˜μ§€λ§Œ κ²°κ³ΌλŠ” λ‹€μŒκ³Ό κ°™λ‹€

 

memberRepository λŠ” 단 ν•œλ²ˆλ§Œ ν˜ΈμΆœλœλ‹€.

@Configuration κ³Ό λ°”μ΄νŠΈ μ½”λ“œ μ‘°μž‘

Spring 의 Bean 은 기본적으둜 싱글톀 Scope λ₯Ό κ°€μ§„λ‹€κ³  이미 μ„€λͺ…ν–ˆλ‹€. Spring 은 이λ₯Ό μ–΄λ–»κ²Œ 보μž₯ν• κΉŒ?

CGLIB ν”„λ‘μ‹œλ₯Ό 톡해 싱글톀을 보μž₯ν•œλ‹€. @Configuration이 뢙은 클래슀의 Bean 듀을 μŠ€ν”„λ§μ€ λŸ°νƒ€μž„μ‹œ, CGLIB Proxy 둜 κ°μ‹Έμ„œ λ©”μ„œλ“œ ν˜ΈμΆœμ„ κ°€λ‘œμ±ˆλ‹€. 즉, @Configuration 클래슀 λ‚΄μ—μ„œ λ‹€λ₯Έ @Bean λ©”μ„œλ“œλ₯Ό 호좜 ν•˜λ”λΌλ„, μ‹€μ œλ‘œ ν•΄λ‹Ή λ©”μ„œλ“œκ°€ μ‹€ν–‰ λ˜λŠ” 것이 μ•„λ‹Œ, ν”„λ‘μ‹œκ°€ κ°€λ‘œμ±„λŠ” ν˜•μ‹μœΌλ‘œ μž‘λ™ν•˜λŠ” 것이닀.

CGLIBλ₯Ό ν†΅ν•œ ν”„λ‘μ‹œ 생성

  1. Spring 은 원본 @Configuration 클래슀λ₯Ό μƒμ†λ°›λŠ” ν”„λ‘μ‹œ μ„œλΈŒν΄λž˜μŠ€λ₯Ό μƒμ„±ν•œλ‹€. 이 ν”„λ‘μ‹œ ν΄λž˜μŠ€λŠ” 원본 클래슀의 λͺ¨λ“  λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ“œ ν•œλ‹€
  2. CGLIBλŠ” λ°”μ΄νŠΈ μ½”λ“œ μˆ˜μ€€μ—μ„œ 클래슀의 λ©”μ„œλ“œλ₯Ό μ‘°μž‘ν•˜λŠ” μƒˆλ‘œμš΄ κΈ°λŠ₯을 μΆ”κ°€ν•˜μ—¬, λ©”μ„œλ“œ ν˜ΈμΆœμ„ κ°€λ‘œμ±„κ±°λ‚˜ μ›ν•˜λŠ” λ‘œμ§μ„ μ‚½μž…ν•œλ‹€.
  3. μ˜€λ²„λΌμ΄λ“œλœ @Bean λ©”μ„œλ“œ 듀은 μ‹€μ œλ‘œλŠ” Spring μ»¨ν…Œμ΄λ„ˆμ—μ„œ Bean 을 쑰회 / μƒμ„±ν•˜λŠ” λ‘œμ§μ„ ν¬ν•¨ν•œλ‹€. 이둜써, 클래슀의 λ©”μ„œλ“œ ν˜ΈμΆœμ‹œ, 본문이 μ•„λ‹Œ μ»¨ν…Œμ΄λ„ˆλ₯Ό 톡해 κ΄€λ¦¬λœ Bean 이 λ°˜ν™˜λ˜λŠ” 것이닀.

λ™μž‘ κ³Όμ •

  1. μ»¨ν…Œμ΄λ„ˆ μ΄ˆκΈ°ν™”μ‹œ, memberService() κ°€ 호좜되고, 이 μ•ˆμ—μ„œ memberRepository() κ°€ ν˜ΈμΆœλœλ‹€. μ΄λ•Œ, ν”„λ‘μ‹œ λ©”μ„œλ“œλŠ” μ»¨ν…Œμ΄λ„ˆμ— memberRepository Bean 이 μ‘΄μž¬ν•˜λŠ”μ§€ ν™•μΈν•˜λŠ”λ°, μ—†μœΌλ―€λ‘œ, super.memberRepository() λ₯Ό ν˜ΈμΆœν•˜μ—¬ 원본 λ©”μ„œλ“œμ˜ 본문을 μ‹€ν–‰ν•˜κ³  Bean 을 생성 및 μ»¨ν…Œμ΄λ„ˆμ— λ“±λ‘ν•˜κ²Œ λœλ‹€.
  2. memberRepository 의 λ‘λ²ˆμ§Έ, μ„Έλ²ˆμ§Έ ν˜ΈμΆœμ‹œ, ν”„λ‘μ‹œ λ©”μ„œλ“œλŠ” μ»¨ν…Œμ΄λ„ˆμ— ν•΄λ‹Ή bean 이 이미 μ‘΄μž¬ν•¨μ„ ν™•μΈν•˜κ³ , 원본 λ©”μ„œλ“œμ˜ 본문을 μ‹€ν–‰ν•˜λŠ” λŒ€μ‹ , μ»¨ν…Œμ΄λ„ˆμ— λ“±λ‘λœ 기쑴의 싱글톀 bean 을 λ°˜ν™˜ν•˜κ²Œ λœλ‹€.

    @Test
    void configurationDeep(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        
        AppConfig bean = ac.getBean(AppConfig.class);
        
        System.out.println("bean = " + bean.getClass());
    }
  • 사싀 AnnotationConfigApplicationContext 에 νŒŒλΌλ―Έν„°λ‘œ λ„˜κΈ΄ 값은 Spring Bean 으둜 등둝이 λœλ‹€.

좜λ ₯κ²°κ³Ό


bean = class hello.core.AppConfig$$SpringCGLIB$$0

 

즉, μš°λ¦¬κ°€ μ‚¬μš©ν•˜λŠ” ν΄λž˜μŠ€λŠ”, μš°λ¦¬κ°€ μƒμ„±ν•œ ν΄λž˜μŠ€κ°€ μ•„λ‹Œ, CGLIB λΌλŠ” λ°”μ΄νŠΈμ½”λ“œ μ‘°μž‘ 라이브러리λ₯Ό μ‚¬μš©ν•΄μ„œ AppConfig λ₯Ό 상속받은 μž„μ˜μ˜ λ‹€λ₯Έ 클래슀λ₯Ό μŠ€ν”„λ§ 빈으둜 λ“±λ‘ν•œ 것이닀.

이λ₯Ό 톡해 싱글톀이 보μž₯λœλ‹€.

ν•œλ²ˆ @Configuration μ–΄λ…Έν…Œμ΄μ…˜μ„ μ§€μš°κ³  μ‹€ν–‰ν•΄ 보자

 

이전과 달리 memberRepository κ°€ 3번 ν˜ΈμΆœλ˜λŠ”κ²ƒμ„ λ³Ό 수 μžˆλ‹€.

 

정리

  • 싱글톀 νŒ¨ν„΄μ€ 객체 μž¬μ‚¬μš© μΈ‘λ©΄μ—μ„œ νš¨μœ¨μ μ΄μ§€λ§Œ, 직접 κ΅¬ν˜„ μ‹œ μœ μ—°μ„±, ν…ŒμŠ€νŠΈμ„±, μœ μ§€λ³΄μˆ˜μ„± μΈ‘λ©΄μ—μ„œ 어렀움을 κ²ͺλŠ”λ‹€.
  • μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” λ³„λ„μ˜ κ΅¬ν˜„ 없이 Bean을 μ‹±κΈ€ν†€μœΌλ‘œ 관리해 이 문제λ₯Ό ν•΄κ²°ν•œλ‹€.
  • μ‹±κΈ€ν†€μœΌλ‘œ Bean을 관리할 λ•ŒλŠ” μƒνƒœλ₯Ό κ°€μ§€μ§€ μ•Šλ„λ‘ μ£Όμ˜ν•΄μ•Ό ν•œλ‹€.
  • @Configurationκ³Ό CGLIB ν”„λ‘μ‹œλ₯Ό 톡해 μŠ€ν”„λ§μ€ λ™μΌν•œ Bean을 μš”μ²­ μ‹œ 항상 같은 μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜λŠ” λ©”μ»€λ‹ˆμ¦˜μ„ κ°–μΆ”κ³  μžˆλ‹€.
  • 닀쀑 μ‚¬μš©μž μš”μ²­μ΄ λ°œμƒν•˜λŠ” μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜ ν™˜κ²½μ—μ„œλŠ” 특히 μ£Όμ˜ν•΄μ•Ό ν•˜λ©°, Stateless 섀계λ₯Ό 톡해 μ•ˆμ „ν•˜κ³  일관성 μžˆλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ ꡬ좕할 수 μžˆλ‹€.