Spring์์์ singleton pattern์ ์์๋ณด๊ธฐ ์ด์ ์
๋ฅผ ์ ํํ๋ฉด ๋ ์ดํดํ๋๋ฐ ์ข๋ค.
Singleton Container
์คํ๋ง์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ณ๋ค๋ฅธ ์ค์ ์ ํ์ง ์์ผ๋ฉด ๋ด๋ถ์์ ์์ฑํ๋ ๋น ์ค๋ธ์ ํธ๋ฅผ ๋ชจ๋ ์ฑ๊ธํค์ผ๋ก ๋ง๋ ๋ค.
์คํ๋ง์ ์๋ฒ ํ๊ฒฝ์์ ์ฑ๊ธํค์ด ๋ง๋ค์ด์ ธ ์๋น์ค ์ค๋ธ์ ํธ ๋ฐฉ์์ผ๋ก ์ฌ์ฉ๋๋ ๊ฒ์ ์ ๊ทน ์ง์งํ๋ค. ํ์ง๋ง ์๋ฐ์ ๊ธฐ๋ณธ์ ์ธ ์ฑ๊ธํค ํจํด์ ๊ตฌํ ๋ฐฉ์์ ์ฌ๋ฌ๊ฐ์ง ๋จ์ ์ด ์๊ธฐ๋๋ฌธ์ ์คํ๋ง์ ์ง์ ์ฑ๊ธํค ํํ์ ์ค๋ธ์ ํธ๋ฅผ ๋ง๋ค๊ณ ๊ด๋ฆฌํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
์คํ๋ง ์ปจํ
์ด๋๋ ์ฑ๊ธํค ์ปจํ
์ด๋ ์ญํ ์ ํ๋ฉฐ, ์ด๋ ๊ฒ ์ฑ๊ธํค ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ๊ด๋ฆฌํ๋ ๊ธฐ๋ฅ์ Singleton Registry๋ผ๊ณ ํ๋ค.
Singleton Registry์ ์ฅ์ ์ ํ๋ฒํ ์๋ฐ ํด๋์ค์ด๋๋ผ๋ IoC๋ฐฉ์์ ์ปจํ
์ด๋๋ฅผ ์ฌ์ฉํด ์์ฑ๊ณผ ๊ด๊ณ์ค์ , ์ฌ์ฉ ๋ฑ์ ๋ํ ์ ์ด๊ถ์ ์์ฝ๊ฒ ์ฑ๊ธํค ๋ฐฉ์์ผ๋ก ๋ง๋ค์ด์ ธ ๊ด๋ฆฌ๋๊ฒ ํ ์ ์๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ํ
์คํธ ํ๊ฒฝ์์๋ ์์ ๋กญ๊ฒ ์ค๋ธ์ ํธ๋ฅผ ๋ง๋ค ์ ์๊ณ , ํ
์คํธ๋ฅผ ์ํ ๋ชฉ์ ์ผ๋ก ์ค๋ธ์ ํธ๋ฅผ ๋์ฒดํ๋ ๊ฒ๋ ๊ฐ๋จํ๋ค.
์ฆ, ์ฑ๊ธํด ํจํด์ ๋จ์ ์ ํด๊ฒฐํ๋ฉด์ ๊ฐ์ฒด๋ฅผ ์ฑ๊ธํค ๋ฐฉ์์ผ๋ก ์ ์ง ํ ์ ์๋ค.
( 1. ์ฑ๊ธํค ํจํด์ ์ํ ์ง์ ๋ถํ ์ฝ๋๊ฐ ์ถ๊ฐ๋์ง ์์. 2. DIP, OCP, ํ
์คํธ, private ์์ฑ์๋ก ๋ถํฐ ์์ ๋กญ๊ฒ ์ฑ๊ธํค ์ฌ์ฉ ๊ฐ๋ฅ)
์ฑ๊ธํค์ด ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์ ์๋น์ค ํํ์ ์ค๋ธ์ ํธ๋ก ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ์๋ stateless ๋ฐฉ์์ผ๋ก ๋ง๋ค์ด์ ธ์ผํ๋ค. ์ด๋๋ ์ฝ๊ธฐ์ ์ฉ ๊ฐ์ด๋ผ๋ฉด ์ด๊ธฐํ ์์ ์ ์ธ์คํด์ค ๋ณ์์ ์ ์ฅํด๋๊ณ ๊ณต์ ํ๋ ๊ฒ์ ๋ฌธ์ ์๋ค. ๋ง์ฝ ๊ฐ ์์ฒญ์ ๋ํ ์ ๋ณด๋, DB ์๋ฒ์ ๋ฆฌ์์ค๋ก ๋ถํฐ ์์ฑํ ์ ๋ณด๋ ํ๋ผ๋ฏธํฐ์ ๋ก์ปฌ ๋ณ์, ๋ฆฌํด ๊ฐ์ ์ด์ฉํ๋ฉด ๋๋ค. ๋ฉ์๋ ํ๋ผ๋ฏธํฐ๋, ๋ฉ์๋ ์์์ ์์ฑ๋๋ ๋ก์ปฌ ๋ณ์๋ ๋งค๋ฒ ์๋ก์ด ๊ฐ์ ์ ์ฅํ ๋
๋ฆฝ์ ์ธ ๊ณต๊ฐ์ด ๋ง๋ค์ด์ง๊ธฐ ๋๋ฌธ์ ์ฑ๊ธํค์ด๋ผ๊ณ ํด๋ ๋ฌธ์ ์๋ค.
Spring์์์ Singleton Pattern
spring์ bean๋ค์ Bean Factory์ ์ํด์ ๊ด๋ฆฌ๋๊ณ ์์ผ๋ฉฐ, ๊ธฐ๋ณธ์ ์ผ๋ก ์ด๋ฌํ bean์ ์๋ช
์ฃผ๊ธฐ์ scope๋ singleton์ ๋ฐ๋ฅด๊ณ ์๋ค. Spring Boot์์๋ ๋ณ๋์ ์ค์ ์ด ์๋ค๋ฉด, DefaultListableBeanFactory
๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๋ฉฐ, resolveBean
๋ฉ์๋๋ฅผ ๋ณด๋ฉด ์ ์ ์๋ค.
@Nullable
private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) {
// 1. ๋ฑ๋ก๋์ด์๋ bean๋ค์ ์ด๋ฆ์ ๊ฒ์
NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);
// 2. ๋ฑ๋ก๋์ด์๋ค๋ฉด ํด๋น bean์ ๋ฆฌํด(์ธ์คํด์ค)
if (namedBean != null) {
return namedBean.getBeanInstance();
}
// 3. ๋ค๋ฅธ beanfactory์์ ์์ฒญํ bean ์ฐพ๊ธฐ
BeanFactory parent = getParentBeanFactory();
if (parent instanceof DefaultListableBeanFactory) {
return ((DefaultListableBeanFactory) parent).resolveBean(requiredType, args, nonUniqueAsNull);
}
else if (parent != null) {
ObjectProvider<T> parentProvider = parent.getBeanProvider(requiredType);
if (args != null) {
return parentProvider.getObject(args);
}
else {
return (nonUniqueAsNull ? parentProvider.getIfUnique() : parentProvider.getIfAvailable());
}
}
// 4. ์ฐพ์ง ๋ชปํ์ ์ null ๋ฐํ
return null;
}
ํ์ง๋ง ์ฌ๊ธฐ์์๋ private static ์ ๊ทผ ์ ์ด์๋ฅผ ํตํ singleton ํจํด์ ์ฐพ์๋ณผ ์ ์๋ค.
์ํฐ ํจํด
์ํฐ ํจํด์ด๋, ์ต๊ด์ ์ผ๋ก ๋ง์ด ์ฌ์ฉํ๋ ํจํด์ด์ง๋ง ์ฑ๋ฅ, ๋๋ฒ๊น
, ์ ์ง๋ณด์, ๊ฐ๋
์ฑ ์ธก๋ฉด์์ ๋ถ์ ์ ์ธ ์ํฅ์ ์ค ์ ์์ด ์ง์ํ๋ ํจํด์ด๋ค.
Singleton ํจํด์ ๋ค์๊ณผ ๊ฐ์ ๋จ์ ์ด ์๋ค.
private ์์ฑ์๋ฅผ ๊ฐ์ง๊ณ ์์ด ์์์ ํ ์ ์๋ค.( ๋คํ์ฑ ์ ๊ณต ๋ถ๊ฐ๋ฅ, ๊ฐ์ฒด์งํฅ ์ค๊ณ ์ ์ฉ ๋ถ๊ฐ)
์๋ฒํ๊ฒฝ์์๋ 1๊ฐ์ instance๋ฅผ ๋ณด์ฅํ์ง ๋ชปํ๋ค.
์ ์ญ ์ํ๋ฅผ ๋ง๋ค ์ ์๊ธฐ ๋๋ฌธ์ ๋ฐ๋์งํ์ง ๋ชปํ๋ค.
singleton์ ์ด๋์์๋ ์ง ๋๊ตฌ๋ ์ ๊ทผํด ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก, ๊ฐ์ฒด์งํฅ์์ ๊ถ์ฅํ์ง ๋์ง ์๋ ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ์ด๋ค.
์ด๋ฌํ ์ด์ ๋ก Spring์์ ์ง์ ์ฑ๊ธํค ํจํด์ ์ฌ์ฉํ์ง ์์ผ๋ฉฐ, Singleton Registry ๋ฐฉ์์ ์ฌ์ฉํ๋ค.
Singleton Registry / Singleton Pattern ์ฃผ์์
๊ฐ์ฒด ์ธ์คํด์ค๋ฅผ ํ๋๋ง ์์ฑํด์ ๊ณต์ ํ๋ ์ฑ๊ธํค ๋ฐฉ์์ ์ฌ๋ฌ ํด๋ผ์ด์ธํธ๊ฐ ํ๋์ ๊ฐ์ ๊ฐ์ฒด ์ธ์คํด์ค๋ฅผ ๊ณต์ ํ๊ธฐ ๋๋ฌธ์ ์ฑ๊ธํค ๊ฐ์ฒด๋ ์ํ๋ฅผ ์ ์ง (stateful)ํ๊ฒ ์ค๊ณํ๋ฉด ์๋๋ค. ๋ฌด์ํ(stateless)๋ก ์ค๊ณํด์ผ ํ๋ค!
ํน์ ํด๋ผ์ด์ธํธ์ ์์กด์ ์ธ ํ๋๊ฐ ์์ผ๋ฉด ์๋๋ค.
ํน์ ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ ํ๋๊ฐ ์์ผ๋ฉด ์๋๋ค!
๊ฐ๊ธ์ ์ฝ๊ธฐ๋ง ๊ฐ๋ฅํด์ผ ํ๋ค.
ํ๋ ๋์ ์ ์๋ฐ์์ ๊ณต์ ๋์ง ์๋, ์ง์ญ๋ณ์, ํ๋ผ๋ฏธํฐ, ThreadLocal ๋ฑ์ ์ฌ์ฉํด์ผ ํ๋ค.
์คํ๋ง ๋น์ ํ๋์ ๊ณต์ ๊ฐ์ ์ค์ ํ๋ฉด ์ ๋ง ํฐ ์ฅ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
@Configuration
/**
* ์ ํ๋ฆฌ์ผ์
์ ์ค์ ๋์์ ํ์ํ ๊ตฌํ ๊ฐ์ฒด ์์ฑ
* ์์ฑํ ๊ฐ์ฒด ์ธ์คํด์ค์ ์ฐธ์กฐ๋ฅผ ์์ฑ์๋ฅผ ํตํด ์ฃผ์
ํด์ค๋ค.
* @Configuration ์ด๋
ธํ
์ด์
์ผ๋ก @Bean์ด ์ฑ๊ธํค์ผ๋ก ๊ด๋ฆฌ๋ ์ ์๊ฒ ํด์ค๋ค.
*/
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
System.out.println("AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
System.out.println("AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), getDiscountPolicy());
}
@Bean
public DiscountPolicy getDiscountPolicy() {
return new RateDiscountPolicy();
}
}
package dh0023.springcore.singleton;
import dh0023.springcore.config.AppConfig;
import dh0023.springcore.member.repository.MemberRepository;
import dh0023.springcore.member.service.MemberServiceImpl;
import dh0023.springcore.order.service.OrderServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ConfigurationSingletonTest {
@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);
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
System.out.println("memberRepository = " + memberRepository);
System.out.println("memberRepository1 = " + memberRepository1);
System.out.println("memberRepository2 = " + memberRepository2);
Assertions.assertThat(memberRepository).isSameAs(memberRepository1);
Assertions.assertThat(memberRepository).isSameAs(memberRepository2);
}
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
// bean = class dh0023.springcore.config.AppConfig$$EnhancerBySpringCGLIB$$2a067523
}
}
์์ํ ํด๋์ค๋ผ๋ฉด class dh0023.springcore.config.AppConfig
๋ก ์ถ๋ ฅ๋์ด์ผํ๋ค. ํ์ง๋ง, @Configuration
์ด CGLIB ๋ฐ์ดํธ ์ฝ๋ ์กฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด AppConfig
๋ฅผ ์์๋ฐ์ ์์์ ํด๋์ค(AppConfig@CGLIB
)๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ค.
@Bean
์ด ๋ถ์ ๋ฉ์๋๋ง๋ค ์ด๋ฏธ ์คํ๋ง ๋น์ด ์กด์ฌํ๋ค๋ฉด, ์กด์ฌํ๋ ๋น์ ๋ฐํํ๊ณ , ์กด์ฌํ์ง ์๋๋ค๋ฉด ์๋ก ์์ฑํ๋ ๋ก์ง์ด ํฌํจ๋์ด ์์ ๊ฒ์ผ๋ก ์์๋๋ค.
๋ง์ฝ @Configuration
์ ์ค์ ํ์ง ์๊ณ @Bean
๋น์ ๋ฑ๋กํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
/**
* ์ ํ๋ฆฌ์ผ์
์ ์ค์ ๋์์ ํ์ํ ๊ตฌํ ๊ฐ์ฒด ์์ฑ
* ์์ฑํ ๊ฐ์ฒด ์ธ์คํด์ค์ ์ฐธ์กฐ๋ฅผ ์์ฑ์๋ฅผ ํตํด ์ฃผ์
ํด์ค๋ค.
* @Configuration ์ด๋
ธํ
์ด์
์ผ๋ก @Bean์ด ์ฑ๊ธํค์ผ๋ก ๊ด๋ฆฌ๋ ์ ์๊ฒ ํด์ค๋ค.
*/
public class AppConfig {
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
System.out.println("AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
System.out.println("AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), getDiscountPolicy());
}
@Bean
public DiscountPolicy getDiscountPolicy() {
return new RateDiscountPolicy();
}
}
package dh0023.springcore.singleton;
import dh0023.springcore.config.AppConfig;
import dh0023.springcore.member.repository.MemberRepository;
import dh0023.springcore.member.service.MemberServiceImpl;
import dh0023.springcore.order.service.OrderServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ConfigurationSingletonTest {
@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);
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
System.out.println("memberRepository = " + memberRepository);
System.out.println("memberRepository1 = " + memberRepository1);
System.out.println("memberRepository2 = " + memberRepository2);
Assertions.assertThat(memberRepository).isSameAs(memberRepository1);
Assertions.assertThat(memberRepository).isSameAs(memberRepository2);
}
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
// bean = class dh0023.springcore.config.AppConfig
}
}
@Configuration
์ ๋ฑ๋กํ์ง ์์๋, @Bean
์ผ๋ก ์คํ๋ง ๋น๋ฑ๋ก์ด ๊ฐ๋ฅํ๋, ๋ฐ์ดํธ ์กฐ์์ ํ์ง ์๋ Class๊ฐ ์์ฑ๋๋ค. ๋ํ, ์ฑ๊ธํค์ด ๋ณด์ฅ๋์ง ์๋๋ค.
์ฐธ๊ณ