μŠ€ν”„λ§ κΈ°λ³Έ 볡슡 2 - 싱글톀과 νŒ¨ν„΄κ³Ό 싱글톀 μ»¨ν…Œμ΄λ„ˆ

1. κ°œμš”

μ—΄μ‹¬νžˆ κ°œλ°œν•˜μ—¬ λŸ°μΉ­ν•œ μ‚¬μ΄λ“œ ν”„λ‘œμ νŠΈ μ„œλΉ„μŠ€κ°€ μž…μ†Œλ¬Έμ„ νƒ€μ„œ 이용자 μˆ˜κ°€ 폭발적으둜 μ¦κ°€ν•˜κ³  μžˆλ‹€κ³  μƒκ°ν•΄λ³΄μž. μ—„μ²­λ‚œ νŠΈλž˜ν”½μ„ 감당해야 ν•˜λŠ” 상황. μŠ€ν”„λ§μ€ μ–΄λ–»κ²Œ μ΄λŸ¬ν•œ μˆ˜λ§Žμ€ λ™μ‹œ μš”μ²­μ„ 감당할 수 μžˆμ„κΉŒ? μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μœ„ν•΄ νƒ„μƒν–ˆλ˜ μŠ€ν”„λ§μ΄ μ·¨ν–ˆλ˜ 생쑴 μ „λž΅μ€ λ¬΄μ—‡μ΄μ—ˆμ„κΉŒ? 보톡 아무리 λ””μžμΈ νŒ¨ν„΄μ— 관심이 μ—†λŠ” κ°œλ°œμžλΌλ„, μŠ€ν”„λ§μ„ μ‚¬μš©ν•˜κ³  μžˆλ‹€λ©΄ ν•œλ²ˆμ―€ λ“€μ–΄λ³Έ 싱글톀 νŒ¨ν„΄, 그리고 더 λ‚˜μ•„κ°€μ„œ μŠ€ν”„λ§μ΄ μ–΄λ– ν•œ λ°©μ‹μœΌλ‘œ 싱글톀을 μœ μ§€ν•˜λŠ”μ§€ 싱글톀 μ»¨ν…Œμ΄λ„ˆκΉŒμ§€ ν•™μŠ΅ν•΄λ³΄λ„λ‘ ν•˜μž.

2. μ‹±κΈ€ν†€μ˜ κ°œμš”

싱글톀 νŒ¨ν„΄μ΄λž€ μ΄λ¦„μ—μ„œ μœ μΆ”ν•  수 μžˆλ“―, 클래슀의 μΈμŠ€ν„΄μŠ€κ°€ ν•œκ°œλ§Œ μƒμ„±λ˜λ„λ‘ 보μž₯ν•˜λŠ” λ””μžμΈ νŒ¨ν„΄μ΄λ‹€. κΈ°λ³Έ μƒμ„±μžλ₯Ό private 으둜 μ œν•œν•˜κ³  μŠ€νƒœν‹± μ˜μ—­μ— 생성해둔 ν•˜λ‚˜μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό 톡해 객체λ₯Ό μ‚¬μš©ν•˜κ²Œ ν•¨μœΌλ‘œμ¨ ν•˜λ‚˜μ˜ 객체λ₯Ό κ³„μ†ν•΄μ„œ μ‚¬μš©ν•˜κ²Œ ν•œλ‹€. μ•„μ£Ό 유λͺ…ν•œ λ””μžμΈνŒ¨ν„΄μ΄λ―€λ‘œ μ‰½κ²Œ 예제 μ½”λ“œλ₯Ό 톡해 직접 ν™•μΈν•΄λ³΄μž.

public class SingletonService {

    // 1. static μ˜μ—­μ— λΆˆλ³€ 객체λ₯Ό 미리 ν•œκ°œ μƒμ„±ν•œλ‹€.
    private static final SingletonService instance = new SingletonService();

    // 2. public μ ‘κ·Ό λ©”μ„œλ“œλ₯Ό 톡해 였직 이 λ©”μ„œλ“œλ₯Ό ν†΅ν•΄μ„œλ§Œ 객체에 μ ‘κ·Ό κ°€λŠ₯ν•˜λ„λ‘ ν•œλ‹€.

    public static SingletonService getInstance() {
        return instance;
    }
    // 3. μƒμ„±μžλ₯Ό private μ ‘κ·Όμ œμ–΄μžλ‘œ λ§‰μŒμœΌλ‘œμ¨ μ™ΈλΆ€μ—μ„œ new ν‚€μ›Œλ“œλ₯Ό ν†΅ν•œ 객체 생성을 λ°©μ§€ν•œλ‹€.
    private SingletonService() {};

    public void singletonObjectCall() {
        System.out.println("["+this+"] Singleton Object Call");
    }
}

κ·Έλ ‡λ‹€λ©΄ 싱글톀 νŒ¨ν„΄μ„ 톡해 정말 ν•˜λ‚˜μ˜ κ°μ²΄λ§Œμ„ μ΄μš©ν•˜λŠ”μ§€ ν…ŒμŠ€νŠΈμ½”λ“œλ‘œ ν™•μΈν•΄λ³΄μž.

public class SingletonServiceTest {

    @Test
    @DisplayName("[ν…ŒμŠ€νŠΈ] μžλ°” 싱글톀 ν…ŒμŠ€νŠΈ")
    void singletonTest() {
        // private κΈ°λ³Έμƒμ„±μžλ₯Ό 톡해 λ‹€μŒκ³Ό 같이 μ‹ κ·œ 객체λ₯Ό μ„ μ–Έν•  수 μ—†λ‹€.
//        SingletonService singletonService = new SingletonService();

        // λ‹€μŒκ³Ό 같이 λ‘κ°œμ˜ 싱글톀 μ„œλΉ„μŠ€ 객체λ₯Ό μ„ μ–Έν•œλ‹€κ³  κ°€μ •ν•˜μž.
        SingletonService singletonServiceA = SingletonService.getInstance();
        SingletonService singletonServiceB = SingletonService.getInstance();

        // μ„ μ–Έν•΄ λ‘” λ©”μ„œλ“œλ₯Ό 톡해 참쑰값도 μ‹€μ œλ‘œ ν™•μΈν•΄λ³΄μž.
        singletonServiceA.singletonObjectCall();
        singletonServiceB.singletonObjectCall();

        // isSameAs λ©”μ„œλ“œλ₯Ό 톡해 참쑰값이 같은지 ν…ŒμŠ€νŠΈν•œλ‹€.
        assertThat(singletonServiceA).isSameAs(singletonServiceB);
    }

}

싱글톀 ν…ŒμŠ€νŠΈμ½”λ“œμ˜ μ‹€ν–‰ κ²°κ³Ό

ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό 톡해 ν™•μΈν•œ κ²°κ³Ό, 싱글톀 νŒ¨ν„΄μ„ 톡해 ν•˜λ‚˜μ˜ 참쑰값을 κ°€μ§€λŠ” μ—¬λŸ¬ 객체λ₯Ό 생성할 수 μžˆμŒμ„ ν™•μΈν–ˆλ‹€. ν•˜μ§€λ§Œ μ΄λ ‡κ²Œ JAVA 싱글톀 νŒ¨ν„΄λ§Œμ„ μ΄μš©ν•˜λŠ” 것은 μ—¬λŸ¬κ°€μ§€ 단점을 가진닀. 

순수 싱글톀 νŒ¨ν„΄μ˜ 단점

1. κ΅¬ν˜„μ΄ λ²ˆκ±°λ‘­λ‹€.

λ‹€λ₯Έ 객체듀과 달리 싱글톀 νŒ¨ν„΄ 섀계λ₯Ό μœ„ν•΄μ„œλ§Œ static μ˜μ—­ μ΄ˆκΈ°ν™”, private κΈ°λ³Έμƒμ„±μž, μΈμŠ€ν„΄μŠ€ νšλ“ λ©”μ„œλ“œ λ“± μ—¬λŸ¬ λΆ€ν’ˆμ΄ ν•„μš”ν•˜λ‹€.

 

2. 객체지ν–₯적인 섀계가 μ–΄λ ΅λ‹€.

μ €λ²ˆ 편의 λ‚΄μš©μ—μ„œ μ‚΄νŽ΄λ³΄μ•˜λ“―, 객체지ν–₯적인 섀계λ₯Ό μœ„ν•΄μ„œλŠ” κ΅¬ν˜„μ²΄ ν΄λž˜μŠ€μ— μ˜μ‘΄ν•˜λ©΄ μ•ˆλœλ‹€. ν•˜μ§€λ§Œ μ•žμ„œ μš°λ¦¬κ°€ μž‘μ„±ν•œ 싱글톀 νŒ¨ν„΄μ€, μΈμŠ€ν„΄μŠ€λ₯Ό μ–»λŠ” κ³Όμ •μ—μ„œ κ΅¬ν˜„μ²΄ ν΄λž˜μŠ€μ— μ˜μ‘΄ν•˜κ³  μžˆλ‹€.

 

3. μœ μ—°μ„±μ΄ 떨어진닀.

싱글톀 νŒ¨ν„΄μ€ private μ ‘κ·Όμ œν•œμžλ₯Ό μ΄μš©ν•˜μ—¬ κΈ°λ³Έ μƒμ„±μžλ₯Ό μ œν•œν•œλ‹€. μ΄λŠ” 상속을 λΆˆκ°€λŠ₯ ν•˜κ²Œ ν•˜λ©°, static μ˜μ—­μ— μ΄ˆκΈ°ν™”κ°€ 이루어지기 λ•Œλ¬Έμ—, ν•„λ“œμ˜ 값을 μ΄ˆκΈ°ν™” ν•˜κ±°λ‚˜ μˆ˜μ •ν•˜λŠ” 것이 μ–΄λ €μš΄ ꡬ쑰이닀.

 

4. ν…ŒμŠ€νŠΈκ°€ μ–΄λ ΅λ‹€.

3번과 μ—°κ³„λ˜λŠ” λ‚΄μš©μ΄μ§€λ§Œ, λ™μ μœΌλ‘œ 객체λ₯Ό μ£Όμž…ν•  수 μ—†κ³  무쑰건 정해진 λ°©μ‹λŒ€λ‘œ μΈμŠ€ν„΄μŠ€λ₯Ό νšλ“ν•΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— λ‹€μ–‘ν•œ ν…ŒμŠ€νŠΈμ— 어렀움이 μžˆλ‹€.

 

λ‹€μŒκ³Ό 같은 λ‹¨μ λ“€λ‘œ 인해 싱글톀은 μ•ˆν‹°νŒ¨ν„΄μ΄ 될 μˆ˜λ„ μžˆλ‹€. 

μ•ˆν‹°νŒ¨ν„΄ : μŠ΅κ΄€μ μœΌλ‘œ 많이 μ‚¬μš©λ˜μ§€λ§Œ 생산성이 λ–¨μ–΄μ§€λŠ” νŒ¨ν„΄μ„ μ˜λ―Έν•œλ‹€.

ν•˜μ§€λ§Œ μŠ€ν”„λ§μ€ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλ₯Ό 톡해 싱글톀 νŒ¨ν„΄μ„ μœ μ§€ν•˜λ©΄μ„œλ„ μ•žμ„œ κΈ°μˆ ν•œ 단점을 극볡할 수 있게 ν•΄μ€€λ‹€.

 

3. μŠ€ν”„λ§ 싱글톀

μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œλŠ” λ™μ‹œμ— λ§Žμ€ νŠΈλž˜ν”½μ΄ λ°œμƒν•  수 있고, λ§Žμ€ 객체가 μž¬μ‚¬μš© 될 κ°€λŠ₯성이 λ†’λ‹€. 예λ₯Ό λ“€μ–΄, νŠΉμ • μ›Ή νŽ˜μ΄μ§€λ₯Ό ν˜ΈμΆœν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•˜λŠ” 컨트둀러 κ°μ²΄λŠ” ν•΄λ‹Ή νŽ˜μ΄μ§€μ— λŒ€ν•œ 방문이 λ°œμƒν•  λ•Œ λ§ˆλ‹€ μƒμ„±ν•˜κ³  μ œκ±°ν•˜κΈ°μ—λŠ” λ„ˆλ¬΄ μ†Œμš”κ°€ μ‹¬ν•œ μ„±κ²©μ˜ 객체이닀.

싱글톀 νŒ¨ν„΄μ΄ μ—†λŠ” 경우

ν•˜μ§€λ§Œ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ— 객체λ₯Ό 등둝할 경우 μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” ν•΄λ‹Ή 객체λ₯Ό ν•œκ°œλ§Œ λ³΄μœ ν•˜μ—¬ μš”μ²­μ΄ μžˆμ„λ•Œλ§ˆλ‹€ μ‚¬μš©μžμ—κ²Œ λ°˜ν™˜ν•΄μ€€λ‹€. 

 

μ•žμ„œ μž‘μ„±ν•œ Config 객체λ₯Ό ν™œμš©ν•˜μ—¬ 정말 μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ”μ„ 톡해 μŠ€ν”„λ§ μ»¨ν…Œμ΄ν„°μ— 등둝 된 객체듀이 λ™μΌν•œ 참쑰값을 가지고 μžˆλŠ”μ§€ ν…ŒμŠ€νŠΈλ₯Ό ν•΄λ³΄μž.

public class ConfigurationSingletonTest {

    @Test
    @DisplayName("[ν…ŒμŠ€νŠΈ] μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆ 싱글톀 ν…ŒμŠ€νŠΈ")
    void autoConfigurationSingletonTest() {

        // given
        AnnotationConfigApplicationContext ac 
        	= new AnnotationConfigApplicationContext(MemberAutoConfig.class);
        MemberService memberServiceA = ac.getBean(MemberService.class);
        MemberService memberServiceB = ac.getBean(MemberService.class);

        // then
        System.out.println("memberServiceA = " + memberServiceA);
        System.out.println("memberServiceB = " + memberServiceB);
        assertThat(memberServiceA).isSameAs(memberServiceB);
    }

}

ν…ŒμŠ€νŠΈμ˜ μ‹€ν–‰ κ²°κ³Ό

μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ”μ„ 톡해 μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ— 등둝 된 객체듀은 λͺ¨λ‘ 같은 참쑰값을 가지고 μžˆμŒμ„ ν™•μΈν•˜μ˜€λ‹€.

 

μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” 이처럼 객체듀을 μ‹±κΈ€ν†€μœΌλ‘œ μœ μ§€ν•˜λ„λ‘ 도와주기 λ•Œλ¬Έμ— 싱글톀 μ»¨ν…Œμ΄λ„ˆλΌκ³ λ„ ν•˜λ©°, μ‹±κΈ€ν†€μœΌλ‘œ 객체λ₯Ό μƒμ„±ν•˜κ³  κ΄€λ¦¬ν•˜λŠ” κΈ°λŠ₯을 싱글톀 λ ˆμ§€μŠ€νŠΈλ¦¬λΌκ³  ν•œλ‹€.

 

μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλ₯Ό 톡해 싱글톀을 μœ μ§€ν•˜λŠ” 경우

 

뢀둝: Configuration 객체와 CGLIB Proxy

1νŽΈμ—μ„œ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ— 객체λ₯Ό λ“±λ‘ν•˜λŠ” λ°©λ²•μœΌλ‘œ μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ”μ„ ν†΅ν•œ 방법 외에도 μˆ˜λ™ 섀정을 ν†΅ν•œ 등둝이 μžˆμŒμ„ ν•™μŠ΅ν•˜μ˜€λ‹€. κ·Έλ•Œ μž‘μ„±ν•œ μˆ˜λ™ 등둝 μ„€μ • Config 파일의 μ†ŒμŠ€λŠ” λ‹€μŒκ³Ό κ°™λ‹€.

@Configuration
public class MemberConfig {

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

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

ν•΄λ‹Ή μ†ŒμŠ€λ₯Ό 잘 μ‚΄νŽ΄λ³΄λ©΄ μ΄μƒν•œ 점이 ν•˜λ‚˜ μžˆλ‹€. λ°”λ‘œ memberRepository 객체λ₯Ό λ‘λ²ˆ ν˜ΈμΆœν•˜μ—¬ λ°˜ν™˜ν•˜λŠ” 것 처럼 보인닀. MemberService 객체 내에 μžˆλŠ” MemberRepository 에 λŒ€ν•œ μ˜μ‘΄μ„±μ„ μ£Όμž…ν•˜κΈ° μœ„ν•΄ λ‹€μŒκ³Ό 같은 상황이 λλŠ”λ°, μŠ€ν”„λ§μ—μ„œ μ‹€μ œλ‘œ MemberRepository 객체λ₯Ό λ‘λ²ˆ μƒμ„±ν•˜λŠ”μ§€ 직접 ν™•μΈν•΄λ³΄μž. 

 

μš°μ„  Config 객체λ₯Ό λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•œλ‹€.

@Configuration
public class MemberConfig {

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

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

}

μ§μž‘ν•œ 것이 λ§žλ‹€λ©΄ MemberConfig 객체 μƒμ„±μ‹œμ— 좜λ ₯λ˜λŠ” λ¬Έμžμ—΄μ΄ MemberRepository에 μ˜μ‘΄μ„±μ„ μ£Όμž…ν•˜λ©΄μ„œ ν•œλ²ˆ, 그리고 MemberService에 μ˜μ‘΄μ„±μ„ μ£Όμž…ν•˜λ©΄μ„œ λ‹€μ‹œ ν•œλ²ˆ 총 두 번 μΆœλ ₯λ˜μ–΄μ•Ό ν•  것이닀. 

 

λ‹€μŒκ³Ό 같이 κ°„λ‹¨ν•œ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆ 호좜 ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜κ³  μ‹€ν–‰ν•΄λ³΄μž.

    @Test
    void configurationTest() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MemberConfig.class);
    ac.close();
    }

ν…ŒμŠ€νŠΈλ₯Ό 싀행해보면 μ˜ˆμƒκ³ΌλŠ” λ‹€λ₯΄κ²Œ memberRepository 생성은 ν•œλ²ˆλ§Œ 호좜되고 싱글톀이 μœ μ§€λ˜κ³  μžˆμŒμ„ μ•Œ 수 μžˆλ‹€. κ·Έλ ‡λ‹€λ©΄ μ–΄λ–»κ²Œ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” λ‘λ²ˆ ν˜ΈμΆœμ„ μ‹œλ„ν•˜λŠ” μ½”λ“œμ—μ„œ ν•œλ²ˆλ§Œ 객체 생성을 ν•˜μ—¬μ„œ 싱글톀을 μœ μ§€ν•  수 μžˆμ—ˆμ„κΉŒ?

 

μœ„μ˜ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό λ‹€μŒκ³Ό 같이 쑰금 μˆ˜μ •ν•˜μ—¬ Config 객체의 클래슀λ₯Ό ν•œλ²ˆ ν™•μΈν•΄λ³΄μž.

    @Test
    void configurationTest() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MemberConfig.class);
        MemberConfig memberConfig = ac.getBean(MemberConfig.class);
		
        //MemberConfig 객체λ₯Ό ν™•μΈν•΄λ³΄μž.
        System.out.println("memberConfig = " + memberConfig);
        ac.close();
    }

λΆ„λͺ… κΈ°λŒ€ν•œ 객체의 ν΄λž˜μŠ€λŠ” MemberConfig 인데 EnHancerBySpringCGLIB κ°€ λΆ™μ–΄μžˆλŠ” νŠΉμ΄ν•œ ν˜•νƒœμ˜ 객체가 좜λ ₯되고 μžˆλ‹€.

 

μ΄λŠ” λ°”μ΄νŠΈμ½”λ“œλ₯Ό μ‘°μž‘ν•˜λŠ” μŠ€ν”„λ§ 라이브러리의 ν•˜λ‚˜λ‘œμ¨ μΌμ’…μ˜ ν”„λ‘μ‹œ 객체이닀.

 

ν”„λ‘μ‹œ κ°μ²΄λž€ ν•΄λ‹Ή 객체λ₯Ό μ§μ ‘μ μœΌλ‘œ μ‚¬μš©ν•˜μ§€ μ•Šκ³  상속을 ν†΅ν•œ λŒ€μ²΄ 객체이닀. 이λ₯Ό μ΄μš©ν•˜λŠ” λ””μžμΈ νŒ¨ν„΄μ„ ν”„λ‘μ‹œ νŒ¨ν„΄μ΄λΌ ν•˜λ©°, ν•˜μ΄λ²„λ„€μ΄νŠΈ, Mockito, μŠ€ν”„λ§ λ“± μ—¬λŸ¬ ν”„λ ˆμž„μ›Œν¬μ—μ„œλ„ μ‚¬μš©λ˜κ³  μžˆλŠ” 경우λ₯Ό μ‰½κ²Œ μ°Ύμ•„λ³Ό 수 μžˆλ‹€.

 

ν”„λ‘μ‹œ νŒ¨ν„΄ 및 CGLIB에 κ΄€ν•΄μ„œλŠ” μΆ”ν›„ μžμ„Έν•˜κ²Œ ν•™μŠ΅ν•˜μ—¬ 정리할 μ˜ˆμ •μ΄λ‹€.

 

ν•œκ°€μ§€ μž¬λ―ΈμžˆλŠ” 점은, MemberConfig 객체에 private μƒμ„±μžλ₯Ό μ •μ˜ν•˜λ©΄ ν”„λ‘μ‹œ 객체λ₯Ό μƒμ„±ν•˜μ§€ λͺ»ν•˜μ—¬ μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€.

MemberConfig 에 private μƒμ„±μžλ₯Ό μ •μ˜ν•˜λŠ” 경우 λ°œμƒν•˜λŠ” μ—λŸ¬

본둠으둜 λŒμ•„μ˜€μžλ©΄ MemberConfig λ₯Ό 상속받은 ν”„λ‘μ‹œκ°μ²΄κ°€ 같은 객체가 λ‘λ²ˆ μƒμ„±λ˜λŠ”μ§€λ₯Ό ν™•μΈν•˜μ—¬ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ— λ“±λ‘ν•΄μ£ΌλŠ” 역할을 ν•˜λŠ” 것이닀. 이λ₯Ό 톡해 μˆ˜λ™ 등둝 방식 λ˜ν•œ 싱글톀을 μœ μ§€ν•  수 μžˆλ‹€.

4. 싱글톀 μ£Όμ˜μ‚¬ν•­

싱글톀 νŒ¨ν„΄(ν˜Ήμ€ 싱글톀 μ»¨ν…Œμ΄λ„ˆ)은 ν•˜λ‚˜μ˜ 객체λ₯Ό μ—¬λŸ¬ ν΄λΌμ΄μ–ΈνŠΈκ°€ κ³΅μœ ν•˜κΈ° λ•Œλ¬Έμ— 항상 λ™μ‹œμ„± 문제λ₯Ό μ£Όμ˜ν•΄μ•Ό ν•œλ‹€. 이λ₯Ό 톡해 μ£Όμ˜ν•΄μ•Ό ν•  사항을 λͺ‡κ°€μ§€ 정리해보면

 

1. νŠΉμ • ν΄λΌμ΄μ–ΈνŠΈμ— 의쑴적이면 μ•ˆλœλ‹€.

2. νŠΉμ • ν΄λΌμ΄μ–ΈνŠΈκ°€ ν•„λ“œμ˜ 값을 λ³€κ²½ν•  수 있으면 μ•ˆλœλ‹€.

3. ν•„λ“œ λŒ€μ‹  μ§€μ—­λ³€μˆ˜, νŒŒλΌλ―Έν„°, ThreadLocal 등을 μ‚¬μš©ν•œλ‹€.

 

싱글톀 객체의 ν•„λ“œμ—μ„œ 값을 직접 닀루어 μƒκΈΈμˆ˜ μžˆλŠ” 문제λ₯Ό μ‚΄νŽ΄λ³΄κΈ° μœ„ν•΄ κ°„λ‹¨ν•œ μ˜ˆμ‹œλ₯Ό μž‘μ„±ν•΄λ³΄μž.

 

이메일을 λ³΄λ‚΄λŠ” μ„œλΉ„μŠ€ λ‘œμ§μ—μ„œ, ν΄λΌμ΄μ–ΈνŠΈκ°€ μš”μ²­ν•˜λŠ” 이메일 μ£Όμ†Œλ₯Ό 곡유 ν•„λ“œμ—μ„œ μ²˜λ¦¬ν•˜κ³  μžˆλ‹€κ³  κ°€μ •ν•œλ‹€.

public class EmailSenderService {

    // Stateful Field 
    private String userEmail;

    public void send(String userEmail) {
        System.out.println("[EMAIL SEND LOGIC] " + userEmail);
        // Stateful Logic
        this.userEmail = userEmail;
    }

    public String getResult() {
        return "[EMAIL SEND LOG] " + this.userEmail;
    }

}

ν•΄λ‹Ή 메일 λ°œμ†‘ μ„œλΉ„μŠ€μ— μ–΄λ– ν•œ μ‹μœΌλ‘œ λ¬Έμ œκ°€ λ°œμƒν•˜λŠ”μ§€ ν…ŒμŠ€νŠΈ ν•˜λŠ” μ½”λ“œλ„ μž‘μ„±ν•΄λ³΄μž.

class EmailSenderServiceTest {

    @Test
    void statefulTest() {
        AnnotationConfigApplicationContext ac 
        	= new AnnotationConfigApplicationContext(EmailSenderConfig.class);

        // 두λͺ…μ˜ ν΄λΌμ΄μ–ΈνŠΈκ°€ 각자 ν˜ΈμΆœν•˜λŠ” 상황이라고 κ°€μ •ν•œλ‹€
        EmailSenderService serviceA = ac.getBean(EmailSenderService.class);
        EmailSenderService serviceB = ac.getBean(EmailSenderService.class);

        String emailA = "addrA@email.com";
        String emailB = "addrB@email.co.kr";

        // μ„œλ‘œ λ‹€λ₯Έ 이메일을 λŒ€μƒμœΌλ‘œ λ‘œμ§μ„ μˆ˜ν–‰ν•œλ‹€.
        serviceA.send(emailA);
        serviceB.send(emailB);

        String resultA = serviceA.getResult();
        String resultB = serviceB.getResult();

        System.out.println("resultA = " + resultA);
        assertThat(resultA).isNotEqualTo(emailA);

        ac.close();
    }


    //μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλ₯Ό μœ„ν•œ Config 객체
    static class EmailSenderConfig {
        @Bean
        public EmailSenderService emailSenderService() {
            return new EmailSenderService();
        }
    }
}

이메일을 λ°œμ†‘ ν›„ κ²°κ³ΌA κ°€ μžμ‹ μ˜ 결과값이 μ•„λ‹Œ λ‹€λ₯Έ ν΄λΌμ΄μ–ΈνŠΈμ˜ κ²°κ³Όλ₯Ό 좜λ ₯ν•˜κ³  μžˆλŠ” 것을 확인할 수 μžˆλ‹€.

 

μƒνƒœ μœ μ§€λ‘œ μΈν•œ λ¬Έμ œλŠ” μ‹€μ œ μƒμš© μ½”λ“œμ—μ„œλŠ” 좔적도 μ–΄λ €μšΈ λΏλ”λŸ¬ μš΄μ˜μ— λ§‰λŒ€ν•œ 손싀을 κ°€μ Έμ˜¬ 수 μžˆμœΌλ―€λ‘œ μ ˆλŒ€λ‘œ Stateful ν•œ μ„€κ³„λŠ” 지양해야 ν•œλ‹€.

 

5. 마치며

μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆκ°€ 싱글톀 λ°©μ‹μœΌλ‘œ μž‘λ™ν•˜κ³  μžˆλ‹€λŠ” 사싀은 μ‰½κ²Œ μ ‘ν•  수 μžˆμ§€λ§Œ μ™œ 싱글톀 방식을 μ‚¬μš©ν•΄μ•Ό ν•˜λŠ”μ§€, 그리고 싱글톀 λ°©μ‹μ˜ μ£Όμ˜μ μ—λŠ” μ–΄λ– ν•œ 것듀이 μžˆλŠ”μ§€λ₯Ό ν™•μΈν•΄λ³΄μ•˜λ‹€. λ¬΄μƒνƒœλ‘œ μ„€κ³„ν•˜λŠ” 것은 비단 λ™μ‹œμ„± 이슈둜 μΈν•œ μž₯μ• λ₯Ό λ°©μ§€ν•˜λŠ”λ°μ—λ„ 도움이 λ˜μ§€λ§Œ, μˆ˜ν‰μ  ν™•μž₯에도 λ§Žμ€ 도움을 μ€€λ‹€. λ”°λΌμ„œ 개발자 μˆœκ°„μ˜ 편의λ₯Ό μœ„ν•΄ μƒνƒœ μœ μ§€λ₯Ό λ‚¨λ°œν•˜λŠ” μ½”λ“œ μž‘μ„±μ€ μ•žμœΌλ‘œλ„ μ΅œλŒ€ν•œ μžμ€‘ν•˜λ„λ‘ ν•˜μž.

 

ν•΄λ‹Ή μ‹œλ¦¬μ¦ˆλŠ” μΈν”„λŸ°μ˜ κΉ€μ˜ν•œ κ°•μ‚¬λ‹˜ [링크]'μŠ€ν”„λ§ 핡심원리 - 기본편'  κ°•μ˜λ₯Ό λ°”νƒ•μœΌλ‘œ
λ‚΄μš©κ³Ό μ†ŒμŠ€λ₯Ό μž¬κ΅¬μ„±ν•˜μ—¬ μž‘μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€.