λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

SpringBoot

[SpringBoot] 빈 등둝과 생성은 λ‹€λ₯΄λ‹€ (feat. BeanDefinition)

πŸ•΅πŸ»‍♀️ κΆκΈˆμ¦μ„ κ°–κ²Œ 된 계기

ν•™μŠ΅ κ³Όμ •μ—μ„œ λ‹€μŒκ³Ό 같은 μ˜ˆμ‹œ μ½”λ“œλ₯Ό λ§ˆμ£Όν•˜κ²Œ 됐닀.

@Test
void test3() {
    StaticApplicationContext context = new StaticApplicationContext();
    context.registerBeanDefinition("printer", new RootBeanDefinition(Printer.class));

    BeanDefinition helloDef = new RootBeanDefinition(Hello.class);
    helloDef.getPropertyValues().addPropertyValue("name", "Spring");
    helloDef.getPropertyValues().addPropertyValue("printer", new RuntimeBeanReference("printer"));
    context.registerBeanDefinition("hello", helloDef);

    Hello hello = context.getBean("hello", Hello.class);
    hello.print();
}

 

μœ„μ˜ μ½”λ“œλ₯Ό 보고, μ™œ #registerbeanDefinition 을 μ‚¬μš©ν–ˆμ„κΉŒ ν•˜λŠ” 의문이 λ“€μ—ˆλ‹€. κ·Έλƒ₯ registerBean 을 ν•˜λ©΄ λ˜λŠ” 게 μ•„λ‹Œκ°€? μ΄λŸ¬ν•œ λ‹¨μˆœν•œ ꢁ금증과 μΌμ’…μ˜ λ°˜λ°œμ‹¬μ— 찾아보기 μ‹œμž‘ν–ˆλ‹€.

 


βœ… BeanDefinition μ΄λž€?

A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.

 

곡식 λ¬Έμ„œμ—μ„œλŠ” BeanDefinition 을 μœ„μ™€ 같이 μ •μ˜ν•˜κ³  μžˆλ‹€. ν•΄μ„ν•˜μžλ©΄ BeanDefinition 은 빈 μΈμŠ€ν„΄μŠ€λ₯Ό μ„€λͺ…ν•˜λŠ” 역할을 ν•œλ‹€. λ˜ν•œ 속성 κ°’κ³Ό μƒμ„±μžμ˜ 인자 κ°’, 그리고 ꡬ체적인 κ΅¬ν˜„μ²΄λ“€μ— μ˜ν•΄ μ œκ³΅λ˜λŠ” 정보듀을 κ°–λŠ”λ‹€λŠ” 것이닀.

BeanDefinition 의 속성 κ°’

μ •μ˜μ—μ„œ μ΄μ•ΌκΈ°ν•˜λŠ”BeanDefinition 의 속성 κ°’μ΄λž€ λ¬΄μ—‡μΌκΉŒ? μ΄λŠ” λ‹¨μˆœνžˆ 객체의 ν•„λ“œκ°’μ΄λΌκ³  μƒκ°ν•˜λ©΄ λœλ‹€.

 

Student λΌλŠ” 객체가 μžˆμ„ λ•Œ name μ΄λΌλŠ” ν•„λ“œμ˜ 값도 속성 값이고, Service κ°€ Repository λ₯Ό μ˜μ‘΄ν•œλ‹€λ©΄ 이 μ—­μ‹œλ„ 속성 값이닀. BeanDefinition μ—μ„œλŠ” 이λ₯Ό 직접 섀정해쀄 수 μžˆλ‹€.

 

BeanDefinition helloDef = new RootBeanDefinition(Hello.class);
helloDef.getPropertyValues().addPropertyValue("name", "Spring");
helloDef.getPropertyValues().addPropertyValue("printer", new RuntimeBeanReference("printer"));

 

μ•žμ„œ μ œμ‹œν•œ μ˜ˆμ‹œ μ½”λ“œμ—μ„œλ„ 이와 같은 λ‚΄μš©μ„ 확인할 수 μžˆλ‹€.

BeanDefinition 의 속성 값듀을 #getPropertyValues λ₯Ό 톡해 λͺ¨λ‘ κ°€μ Έμ˜€κ³ , key 와 value λ₯Ό μ •μ˜ν•΄ #addPropertyValue λ₯Ό 톡해 속성을 μΆ”κ°€ν•œλ‹€.

속성 값이 μ„ΈνŒ…λ˜λŠ” 원리

문득 이 속성 값은 μ–΄λ–»κ²Œ μ„ΈνŒ…λ˜λŠ”μ§€ κΆκΈˆν–ˆλ‹€. key 와 value ν˜•νƒœμ΄λ‹ˆ ν•„λ“œλͺ…μΌκΉŒ? μ•„λ‹ˆλ©΄ setter 의 μ΄λ¦„μΌκΉŒ?

μš°μ„  정닡은 setter 이닀. 

helloDef.getPropertyValues().addPropertyValue("name", "Spring");

public class Hello {
	private final String name; 
	
	public void setName(String name){
	}
}
helloDef.getPropertyValues().addPropertyValue("naaame", "Spring");

public class Hello {
	private final String name; 
	
	public void setNaaame(String name){
	}
}

μœ„μ˜ 두 μ½”λ“œλŠ” λͺ¨λ‘ 정상 μž‘λ™ν•œλ‹€. 즉, setXXX λ₯Ό 톡해 속성이 μ„ΈνŒ…λ˜λŠ” 것이닀.

RuntimeBeanReference

BeanDefinition helloDef = new RootBeanDefinition(Hello.class);
helloDef.getPropertyValues().addPropertyValue("name", "Spring");
helloDef.getPropertyValues().addPropertyValue("printer", new RuntimeBeanReference("printer"));

 

μ•žμ„  μ˜ˆμ‹œ μ½”λ“œμ—λ„ λ‚˜νƒ€λ‚˜ μžˆλ“―, RuntimeBeanReference λ₯Ό 생성해 내뢀에 "printer" λ₯Ό λ‹¨μˆœνžˆ λ¬Έμžμ—΄λ‘œ λ„£κ³  μžˆλ‹€.

κ·ΈλŸΌμ—λ„ μ–΄λ–»κ²Œ Helllo λΌλŠ” 클래슀의 ν•„λ“œμ— μ •μƒμ μœΌλ‘œ Printer ν΄λž˜μŠ€μ— λŒ€ν•œ μ˜μ‘΄μ„± μ£Όμž…μ΄ μ΄λ€„μ§€λŠ” κ²ƒμΌκΉŒ?

 

μš°μ„  RuntimeBeanReference λŠ” 빈 μ„€μ • 정보 쀑 λ‹€λ₯Έ λΉˆμ„ μ°Έμ‘°ν•  ν•„μš”κ°€ μžˆλŠ” 경우 μ‚¬μš©ν•˜λŠ” ν΄λž˜μŠ€μ΄λ‹€. 즉, ‘λ‚˜μ€‘μ— λ‚˜ 이 κ°’ ν•„μš”ν•˜λ‹ˆκΉŒ λŸ°νƒ€μž„μ— μ°Ύμ•„μ„œ μ£Όμž…ν•΄μ€˜~’ ν•˜λŠ” μš”κ΅¬μ‚¬ν•­μΈ 것이닀.

 

λ˜ν•œ λ¬Έμžμ—΄λ‘œλ§Œ μ •μ˜ν•˜λ”λΌλ„ ν•΄λ‹Ή Printer 의 μΈμŠ€ν„΄μŠ€λ₯Ό μ •μƒμ μœΌλ‘œ μ°Ύμ•„ μ£Όμž…ν•  수 μžˆλŠ” μ΄μœ λŠ”, ApplicationContext μ—μ„œ printer λΌλŠ” 이름을 κ°€μ§„ λΉˆμ„ μ°Ύμ•„ μ£Όμž…ν•΄μ£ΌκΈ° λ•Œλ¬Έμ΄λ‹€.


βœ… μ™œ Bean 을 λ“±λ‘ν•˜μ§€ μ•Šκ³  BeanDefinition 을 λ“±λ‘ν• κΉŒ?

μŠ€ν”„λ§ λΆ€νŠΈμ˜ λ³Έμ§ˆμ€ Bean μ΄λΌλŠ” 생각을 ν–ˆλ‹€. 그렇기에 μ•žμ„œ μ΄μ•ΌκΈ°ν–ˆλ˜ κ²ƒμ²˜λŸΌ μ™œ ꡳ이 #registerBean 을 μ‚¬μš©ν•˜μ§€ μ•Šκ³ , #registerBeanDefinition 을 μ‚¬μš©ν•˜λŠ”μ§€μ— λŒ€ν•œ 의문이 λ“€μ—ˆλ‹€.

 

κ·ΈλŸ¬λ‚˜ μ‹€μ œλ‘œ 곡뢀λ₯Ό ν•˜λ‹€ λ³΄λ‹ˆ, μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ”μ΄ μ΄λ€„μ§ˆ λ•Œ 사싀상 λ“±λ‘λ˜λŠ” 것은 Bean 이 μ•„λ‹ˆλΌ BeanDefinition μ΄λΌλŠ” 것을 μ•Œκ²Œ 됐닀. 

 

πŸ™‹ λ‚΄κ°€ μƒκ°ν–ˆλ˜ μŠ€ν”„λ§λΆ€νŠΈμ˜ μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ” κ³Όμ •

 

  1. @Bean 이 뢙은 ν΄λž˜μŠ€λ“€μ„ λͺ¨λ‘ μŠ€μΊ”
  2. ν•΄λ‹Ή 클래슀의 μΈμŠ€ν„΄μŠ€λ“€μ„ λͺ¨λ‘ μƒμ„±ν•΄μ„œ ApplicationContext 에 등둝

β˜€οΈ μ‹€μ œ μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ” κ³Όμ •

  1. μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ” μ‹œμž‘
  2. @Component κ΄€λ ¨ μ–΄λ…Έν…Œμ΄μ…˜μ΄ 뢙은 ν΄λž˜μŠ€λ“€μ— λŒ€ν•œ BeanDefinition 을 λ§Œλ“€μ–΄μ„œ BeanDefinitionRegistry 에 등둝
  3. μ»¨ν…Œμ΄λ„ˆκ°€ μ΄ˆκΈ°ν™” 될 λ•Œ μ μ ˆν•œ μ‹œμ μ— 따라 λΉˆμ„ μƒμ„±ν•œλ‹€

그렇기에 ApplicationContext 에 registerBeanDefinition 을 ν•˜λŠ” 것은 였히렀 더 λ§žλŠ” 수순인 것이닀.


βœ… μ™œ μ •μ˜λ₯Ό λ“±λ‘ν•˜κ³  λ°”λ‘œ 생성은 ν•˜μ§€ μ•ŠλŠ” κ²ƒμΌκΉŒ?

μŠ€ν”„λ§μ—μ„œ λ‹€λ£¨λŠ” λΉˆμ€ μš°λ¦¬κ°€ POJO μ—μ„œ μƒμ„±ν•˜λŠ” 객체의 μΈμŠ€ν„΄μŠ€μ™€λŠ” λ‹€λ₯΄λ‹€. μŠ€ν”„λ§μ—μ„œμ˜ λΉˆμ€ λ§Žμ€ 뢀가적 κΈ°λŠ₯을 μˆ˜ν–‰ν•΄μ•Ό ν•œλ‹€. λ˜ν•œ 이λ₯Ό μœ„ν•΄μ„œλŠ” μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆκ°€ 객체의 생성 μ‹œμ μ΄λ‚˜ 과정을 μ œμ–΄ν•΄μ•Ό ν•œλ‹€.

 

1. μ§€μ—° 생성을 톡해 λ©”λͺ¨λ¦¬μ™€ λ¦¬μ†ŒμŠ€λ₯Ό μ ˆμ•½ν•œλ‹€.

λ°”λ‘œ 빈으둜 λͺ¨λ‘ λ§Œλ“€κ²Œ 되면 λ©”λͺ¨λ¦¬μ™€ λ¦¬μ†ŒμŠ€κ°€ 낭비될 μˆ˜λ„ μžˆλ‹€. μŠ€ν”„λ§ λΆ€νŠΈμ—λŠ” 'μ§€μ—° 생성'이 μ‘΄μž¬ν•œλ‹€.

이λ₯Ό 톡해 ν•΄λ‹Ή λΉˆμ„ μ‹€μ œλ‘œ ν˜ΈμΆœν•  λ•ŒκΉŒμ§€ μΈμŠ€ν„΄μŠ€ν™”ν•˜λŠ” μ‹œμ μ„ λ―Έλ£° 수 μžˆλ‹€. λ”°λΌμ„œ μŠ€ν”„λ§μ—μ„œλŠ” μ •μ˜λ§Œ 등둝해두고 생성 μ‹œμ μ€ λ”°λ‘œ κ΄€λ¦¬ν•˜λ„λ‘ ν•˜λŠ” 것이닀.

 

2. μ˜μ‘΄μ„± κ·Έλž˜ν”„λ₯Ό κ΄€λ¦¬ν•˜κΈ° μœ„ν•΄

μŠ€ν”„λ§μ—μ„œλŠ” μˆœν™˜ μ°Έμ‘°λ₯Ό λ°©μ§€ν•  수 있게 ν•΄μ€€λ‹€. 이가 κ°€λŠ₯ν•œ μ΄μœ λŠ”, 빈의 μ •μ˜λ₯Ό λ¨Όμ € μ­‰ νƒμƒ‰ν•˜λ©° μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆκ°€ μ˜μ‘΄μ„±μ„ νŒŒμ•…ν•΄ μˆœν™˜ μ°Έμ‘°κ°€ μ‘΄μž¬ν•˜λŠ”μ§€ 확인해주기 λ•Œλ¬Έμ΄λ‹€.

λͺ¨λ“  μ •μ˜λ₯Ό ν™•λ³΄ν•œ 뒀에야 μΈμŠ€ν„΄μŠ€ 생성을 ν•˜κΈ° λ•Œλ¬Έμ— 생성 μ‹œμ μ— λŒ€ν•œ 문제λ₯Ό λ°©μ§€ν•  수 μžˆλ‹€.

 

3. ν”„λ‘μ‹œ ꡬ쑰λ₯Ό μœ„ν•΄

μŠ€ν”„λ§μ—μ„œλŠ” ν”„λ‘μ‹œ ꡬ쑰λ₯Ό μ‚¬μš©ν•œλ‹€. 예λ₯Ό λ“€μ–΄, @Configuration 이 뢙은 클래슀의 경우 Bean 을 등둝할 수 μžˆλ‹€. 이 λ•Œ, λ‹¨μˆœνžˆ POJO 둜만 ν•΄λ‹Ή 클래슀λ₯Ό κ΄€λ¦¬ν•˜κ²Œ 되면 Bean 으둜 λ“±λ‘λ˜λŠ” νŠΉμ • 클래슀의 μΈμŠ€ν„΄μŠ€κ°€ μ—¬λŸ¬κ°œκ°€ 될 μˆ˜λ„ μžˆλ‹€.

 

κ·ΈλŸ¬λ‚˜ ν•΄λ‹Ή @Configuration 이 뢙은 클래슀λ₯Ό ν”„λ‘μ‹œλ‘œ κ΄€λ¦¬ν•¨μœΌλ‘œμ„œ λͺ¨λ“  μΈμŠ€ν„΄μŠ€λ“€μ„ μ‹±κΈ€ν„΄μœΌλ‘œ 관리할 수 있게 λœλ‹€.

μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜λŠ” κ³Όμ •μ—μ„œ λΉˆμ„ λ³€ν˜•ν•΄ μ‘°μž‘ν•  수 μžˆμ–΄μ•Ό ν•˜κΈ°μ—, 즉 객체의 μΈμŠ€ν„΄μŠ€ν™”λ₯Ό ν•  κΆŒλ¦¬κ°€ μ˜¨μ „νžˆ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ—κ²Œ μžˆμ–΄μ•Ό ν•˜κΈ°μ— μ •μ˜λ₯Ό λ¨Όμ € λ“±λ‘ν•˜κ²Œ λ˜λŠ” 것이닀.


βœ… #registerBean λ©”μ„œλ“œλŠ” μ™œ μ‘΄μž¬ν•˜λŠ”κ±ΈκΉŒ?

κ·Έλ ‡λ‹€λ©΄ ApplicationContext 에 registerBean λ©”μ„œλ“œκ°€ μ™œ μ‘΄μž¬ν•˜λŠ”μ§€μ— λŒ€ν•œ 의문이 λ“€ 것이닀.

κ·ΈλŸ¬λ‚˜ 사싀 registerBean λ©”μ„œλ“œλ₯Ό μ‹€μ œλ‘œ 확인해보면, λ‚΄λΆ€μ μœΌλ‘œλŠ” BeanDefinition을 λ“±λ‘ν•˜κ³  μžˆλ‹€λŠ” 것을 확인할 수 μžˆλ‹€.

private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
    @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
    @Nullable BeanDefinitionCustomizer[] customizers) {

    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
        return;
    }

	//.. μƒλž΅

   // BeanDefinition 을 등둝
	BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

 

즉 μ΄λŠ” λ‹¨μˆœνžˆ Bean 으둜 μΈμ‹ν•˜κ³  μžˆλŠ” κ°œλ°œμžμ—κ²Œ νŽΈμ˜μ„±μ„ μ œκ³΅ν•˜κΈ° μœ„ν•œ λ©”μ„œλ“œμ— μ§€λ‚˜μ§€ μ•ŠλŠ” 것이닀.


βœ… μš°λ¦¬λŠ” BeanDefinition 을 μ–Έμ œ μ‚¬μš©ν• κΉŒ?

μ‹€μ œλ‘œ μŠ€ν”„λ§λΆ€νŠΈ κ°œλ°œμ„ ν•˜λ©΄μ„œ BeanDefinition 을 μ§μ ‘μ μœΌλ‘œ λ‹€λ£° 일은 거의 μ—†μ—ˆλ‹€. μ™œλƒν•˜λ©΄ μ΄μ œκΉŒμ§€ λͺ¨λ“  λΉˆλ“€μ€ λŒ€λΆ€λΆ„ μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ”μ— μ˜ν•΄ μ²˜λ¦¬ν•  수 μžˆμ—ˆκΈ°μ— λ‚΄λΆ€μ μœΌλ‘œλŠ” BeanDefinition 을 닀루고 μžˆμ„μ§€ λͺ°λΌλ„, 직접 μ‚¬μš©ν•  일은 μ—†μ—ˆκΈ° λ•Œλ¬Έμ΄λ‹€.

 

κ·ΈλŸ¬λ‚˜ μ΄λŠ” 빈 ꡬ성과 μ£Όμž… λŒ€μƒμ΄ 사전에 κ³ μ •λ˜μ–΄ μžˆλŠ” κ²½μš°μ—λ§Œ ν•΄λ‹Ήλœλ‹€. μ™ΈλΆ€ 쑰건에 μ˜ν•΄ λŸ°νƒ€μž„μ— λ§Œμ•½ μ–΄λ–€ λΉˆμ„ μ£Όμž…ν• μ§€ λͺ¨λ₯Έλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒ?

 

예λ₯Ό λ“€μ–΄, json νŒŒμΌμ„ 읽고 κ²½μš°μ— 따라 νŠΉμ • λΉˆμ„ μ£Όμž…ν•΄μ•Ό ν•œλ‹€κ³  κ°€μ •ν•΄λ³΄μž.

{
  "usePaymentService": true,
  "useNotificationService": false
}
if (config.isUsePaymentService()) {
    RootBeanDefinition bd = new RootBeanDefinition(PaymentService.class);
    registry.registerBeanDefinition("paymentService", bd);
}
if (config.isUseNotificationService()) {
    RootBeanDefinition bd = new RootBeanDefinition(NotificationService.class);
    registry.registerBeanDefinition("notificationService", bd);
}

 

λ§Œμ•½ μœ„μ™€ 같이 μ£Όμž…ν•΄μ•Ό ν•˜λŠ” 빈이 κ³ μ •λ˜μ–΄ μžˆμ§€ μ•Šκ³ , μ™ΈλΆ€ 쑰건에 따라 μ£Όμž…ν•΄μ•Ό ν•˜λŠ” λΉˆμ„ λŸ°νƒ€μž„μ—λ§Œ 인식할 수 μžˆλ‹€λ©΄ κ°œλ°œμžκ°€ 직접 BeanDefinition 을 μ •μ˜ν•΄ λ“±λ‘ν•΄μ€˜μ•Ό ν•œλ‹€.


βœ… BeanDefinition 은 μ–Έμ œ Bean 이 될까?

μ •μ˜λ‘œλ§Œ λ‚¨μ•„μžˆμ—ˆλŠ”λ°, μ–Έμ œ μΈμŠ€ν„΄μŠ€ν™”κ°€ λ˜μ–΄ μ‹€μ œ 빈이 λ˜λŠ” 걸까? μ΄λŠ” 크게 두 κ°€μ§€κ°€ μ‘΄μž¬ν•œλ‹€.

 

1. ApplicationContext κ°€ refresh λ˜λŠ” 경우

ApplicationContext κ°€ refresh λ˜λŠ” κ²½μš°μ—λŠ” μ˜μ‘΄μ„± μ£Όμž…κ³Ό μΈμŠ€ν„΄μŠ€ν™”κ°€ μΌμ–΄λ‚œλ‹€. μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ”μ΄ 이뀄지고 λ‚˜λ©΄ λ‚΄λΆ€μ μœΌλ‘œ λ°”λ‘œ refresh κ°€ 되기 λ•Œλ¬Έμ— μš°λ¦¬λŠ” Bean 이 μΈμŠ€ν„΄μŠ€ν™” λ˜μ–΄ μžˆλŠ” μ±„λ‘œ λ§ˆμ£Όν•˜κ²Œ λœλ‹€.

 

2. μ‹€μ œ 빈이 ν˜ΈμΆœλ˜λŠ” 경우 (#getBean)

Bean이 μ§€μ—° μ΄ˆκΈ°ν™” λŒ€μƒμΈ 경우, λ˜λŠ” λŸ°νƒ€μž„μ— μ–΄λ–€ 빈이 μ£Όμž…λ μ§€κ°€ κ²°μ •λ˜λŠ” κ²½μš°μ—λŠ” refresh() 이후에 BeanDefinition이 λ“±λ‘λ˜κΈ°λ„ ν•œλ‹€. 이런 경우, λͺ…μ‹œμ μœΌλ‘œ refresh()λ₯Ό ν˜ΈμΆœν•˜κ±°λ‚˜, μ‹€μ œλ‘œ λΉˆμ„ μš”μ²­ν•˜λŠ” μ‹œμ μ—μ„œμ•Ό μΈμŠ€ν„΄μŠ€ν™”κ°€ 이루어진닀.