πŸƒSpring Beanκ³Ό 생λͺ…μ£ΌκΈ°, μŠ€μ½”ν”„

핡심원리 μ‹œλ¦¬μ¦ˆ

3 minute read

(이 글은 κΉ€μ˜ν•œ λ‹˜μ˜ μŠ€ν”„λ§ 핡심원리 κΈ°λ³ΈνŽΈμ„ κ³΅λΆ€ν•˜κ³  μž‘μ„±ν•œ κΈ€μž…λ‹ˆλ‹€)

μœ λ‹ˆν‹° 3D κ²Œμž„μ—”μ§„μ˜ GameObjectλΌλ˜μ§€ React의 Componentλ₯Ό 보면 λͺ¨λ‘ 생λͺ…μ£ΌκΈ° ν•¨μˆ˜κ°€ μžˆλ‹€. 객체가 생성될 λ•Œ, μ†Œλ©Έλ  λ•Œ, μ—…λ°μ΄νŠΈ 될 λ•Œμ˜ λ™μž‘λ“€μ„ μ •μ˜ν•˜λŠ” 것이닀. μ΄λŠ” Effective C++μ—μ„œ μ„€λͺ…ν•˜λŠ” RAII(Resource Acquisition Is Initialization) κ°œλ…κ³Όλ„ λ§žλ‹Ώμ•„ μžˆλ‹€.

μ–΄λŠκ²ƒμ΄ μ˜€λ¦¬μ§€λ„μΈμ§€λŠ” λͺ°λΌλ„ μŠ€ν”„λ§ Bean μ—­μ‹œ μ΄λŸ¬ν•œ 생λͺ…μ£ΌκΈ° ν•¨μˆ˜λ₯Ό κ°€μ§€κ³  μžˆλ‹€. 그것에 λŒ€ν•œ μ„€λͺ…을 ν•  것이닀.

λ¨Όμ € ν΄λž˜μŠ€μ—λŠ” μƒμ„±μžμ™€ μ†Œλ©ΈμžλΌλŠ” ν›Œλ₯­ν•œ 생λͺ…μ£ΌκΈ° 도ꡬ가 μžˆλŠ”λ° μ™œ λ‹€λ₯Έ μ½œλ°±ν•¨μˆ˜κ°€ ν•„μš”ν•œ κ²ƒμΌκΉŒ? 닡은 μŠ€ν”„λ§μ˜ 경우 Bean의 생성과 μ˜μ‘΄μ„± μ£Όμž…μ˜ μ‹œμ μ΄ λ‹€λ₯Ό 수 있기 λ•Œλ¬Έμ΄λ‹€. (λ¬Όλ‘  κ°€μž₯ 많이 μ‚¬μš©ν•˜λŠ” μƒμ„±μž μ£Όμž…μ˜ κ²½μš°λŠ” 두 μ‹œμ μ΄ λ™μΌν•˜μ—¬ κ±±μ •ν•  ν•„μš”λŠ” μ—†λ‹€)

μœ„μ—μ„œ RAII에 λŒ€ν•΄ μ–ΈκΈ‰ν•˜μ˜€μ§€λ§Œ μŠ€ν”„λ§μ—μ„œλŠ” 쑰금 λ‹€λ₯Έ 철학을 κ°€μ§€κ³  μžˆλŠ”λ“―ν•˜λ‹€. 객체의 생성과 μ΄ˆκΈ°ν™”λ₯Ό λΆ„λ¦¬ν•˜μ—¬ 역할을 λ‚˜λˆ„λŠ” 것이닀. λ‹¨μΌμ±…μž„μ›μΉ™μ„ μƒκ°ν•˜λ©΄ μ˜³μ€ 말일 수 있고, RAII κ°œλ…μ΄ λ‚˜μ˜¨ C++μ—μ„œλŠ” κ°€λΉ„μ§€ 컬렉터가 μ—†μ–΄ μžμ›μ˜ ν•΄μ œκ°€ λ„ˆλ¬΄λ‚˜λ„ μ€‘μš”ν•˜λ‹€λŠ” 배경을 생각해봐야 ν•œλ‹€.

그럼 본격적으둜 생λͺ…주기에 κ΄€λ ¨λœ μ½œλ°±μ— λŒ€ν•΄ μ•Œμ•„λ³΄μž.

  • 상속을 μ΄μš©ν•˜λŠ” 방법
public class NetworkClient implements InitializingBean, DisposableBean {  
   @Override  
   public void afterPropertiesSet() throws Exception {  
      connect();  
   }  
   @Override  
   public void destroy() throws Exception {  
      disConnect();  
   }  
}  

μœ„ μ½”λ“œμ—μ„œ 보듯 InitializingBean, DisposableBean μΈν„°νŽ˜μ΄μŠ€λ₯Ό μƒμ†ν•˜κ³  그에 λ”°λ₯Έ afterPropertiesSet(), destroy()λ₯Ό κ΅¬ν˜„ν•˜λŠ” 방식이닀. ν•˜μ§€λ§Œ 거의 μ‚¬μš©λ˜μ§€ μ•ŠλŠ” 방법인데 μ΄μœ λŠ”, μ € μΈν„°νŽ˜μ΄μŠ€λ“€μ€ μŠ€ν”„λ§μ—μ„œ μ œκ³΅ν•˜λŠ” λ‚΄μš©μ΄λΌ μœ μ—°μ„±μ΄ λ–¨μ–΄μ§€κ³  λ©”μ†Œλ“œλͺ…을 λ³€κ²½ν•  수 μ—†κΈ° λ•Œλ¬Έμ΄λ‹€.

  • Bean μ„€μ •μ‹œ μ§€μ •ν•˜λŠ” 방법
@Configuration  
static class LifeCycleConfig {  
   @Bean(initMethod = "init", destroyMethod = "close")  
   public NetworkClient networkClient() {  
  
...  
  

μ½”λ“œμ—μ„œ 보듯 Bean μ• λ…Έν…Œμ΄μ…˜μ—μ„œ 직접 생λͺ…μ£ΌκΈ° ν•¨μˆ˜λͺ…을 등둝할 수 μžˆλ‹€. μ•žμ„œ λ°©μ‹μ˜ λ¬Έμ œμ˜€λ˜ λ©”μ†Œλ“œλͺ… μ§€μ •λ¬Έμ œλ₯Ό ν•΄κ²°ν•  수 μžˆλ‹€. ν•œκ°€μ§€ 더 비밀이 μžˆλŠ”λ° 이 κΈ°λŠ₯μ—λŠ” λ©”μ†Œλ“œλͺ… μΆ”λ‘  κΈ°λŠ₯이 μžˆμ–΄μ„œ destroyMethodλ₯Ό λ„£μ§€ μ•Šμ•„λ„ close, shutdownκ³Ό 같은 의미의 λ©”μ†Œλ“œλ₯Ό μ°Ύμ•„μ„œ μžλ™μœΌλ‘œ ν˜ΈμΆœν•΄μ€€λ‹€. νŽΈλ¦¬ν• μˆ˜λ„ μžˆμ§€λ§Œ μ—­μ‹œλ‚˜ λͺ…μ‹œμ μΈ 것이 μ’‹λ‹€. κ°œμΈμ μœΌλ‘œλŠ” μ“°μ§€ μ•Šμ„ 것 κ°™λ‹€.

  • @PostConstruct, @PreDestory μ• λ…Έν…Œμ΄μ…˜μ„ ν™œμš©ν•˜λŠ” 방법
@PostConstruct  
public void init() {  
   connect();  
}  
  
@PreDestroy  
public void close() {  
   disConnect();  
}  

λ”± 보면 μ•Œλ‹€μ‹œν”Ό κ°€μž₯ ꢌμž₯λ˜λŠ” 기법이닀. μ΅œμ‹  μŠ€ν”„λ§ νŠΈλ Œλ“œμ— 맞게 μ• λ…Έν…Œμ΄μ…˜μœΌλ‘œ κ°„λ‹¨ν•˜κ²Œ μ •μ˜κ°€ κ°€λŠ₯ν•˜κ³  λ©”μ†Œλ“œλͺ… 지정도 κ°€λŠ₯ν•˜λ©°, 무엇보닀 μžλ°” ν‘œμ€€μœΌλ‘œ λ™μž‘ν•΄μ„œ μŠ€ν”„λ§μ— 쒅속성이 μ—†λ‹€.

Bean μŠ€μ½”ν”„

Bean은 기본적으둜 μ‹±κΈ€ν†€μœΌλ‘œ κ΅¬ν˜„λ˜μ–΄ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆκ°€ μ‚¬λΌμ§ˆ λ•ŒκΉŒμ§€(μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ’…λ£Œλ  λ•ŒκΉŒμ§€) μœ μ§€λœλ‹€. ν•˜μ§€λ§Œ 싱글톀 Bean만 μžˆλŠ” 것은 μ•„λ‹ˆλ©° λ‹€μ–‘ν•œ μŠ€μ½”ν”„λ₯Ό κ°€μ§„ Bean듀이 μ‘΄μž¬ν•œλ‹€. μ΄λ²ˆμ—λŠ” Bean μŠ€μ½”ν”„μ— λŒ€ν•΄ μ•Œμ•„λ³Ό 것이닀.

λ¨Όμ € Bean μŠ€μ½”ν”„μ˜ μ’…λ₯˜μ— λŒ€ν•΄ μ•Œμ•„λ³΄μž.

  • 싱글톀: κΈ°λ³Έ μŠ€μ½”ν”„, μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ˜ μ‹œμž‘κ³Ό μ’…λ£ŒκΉŒμ§€ μœ μ§€λ˜λŠ” κ°€μž₯ 넓은 λ²”μœ„μ˜ μŠ€μ½”ν”„μ΄λ‹€.
  • ν”„λ‘œν† νƒ€μž…: μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” ν”„λ‘œν† νƒ€μž… 빈의 생성과 μ˜μ‘΄κ΄€κ³„ μ£Όμž…κΉŒμ§€λ§Œ κ΄€μ—¬ν•˜κ³  λ”λŠ” κ΄€λ¦¬ν•˜μ§€ μ•ŠλŠ” 맀우 짧은 λ²”μœ„μ˜ μŠ€μ½”ν”„μ΄λ‹€.
  • μ›Ή κ΄€λ ¨ μŠ€μ½”ν”„
    request: μ›Ή μš”μ²­μ΄ λ“€μ–΄μ˜€κ³  λ‚˜κ°ˆλ•Œ κΉŒμ§€ μœ μ§€λ˜λŠ” μŠ€μ½”ν”„μ΄λ‹€.
    session: μ›Ή μ„Έμ…˜μ΄ μƒμ„±λ˜κ³  μ’…λ£Œλ  λ•Œ κΉŒμ§€ μœ μ§€λ˜λŠ” μŠ€μ½”ν”„μ΄λ‹€.
    application: μ›Ήμ˜ μ„œλΈ”λ¦Ώ μ»¨ν…μŠ€νŠΈμ™€ 같은 λ²”μœ„λ‘œ μœ μ§€λ˜λŠ” μŠ€μ½”ν”„μ΄λ‹€.
    websocket: μ›Ή μ†ŒμΌ“κ³Ό λ™μΌν•œ 생λͺ…μ£ΌκΈ°λ₯Ό κ°€μ§€λŠ” μŠ€μ½”ν”„μ΄λ‹€.

μŠ€μ½”ν”„ 지정은 κ°„λ‹¨ν•˜λ‹€. μ—­μ‹œλ‚˜ μ• λ…Έν…Œμ΄μ…˜μ„ ν†΅ν•΄μ„œ μ•„λž˜μ™€ 같이 μ§€μ •ν•  수 μžˆλ‹€.

@Scope("prototype")  
@Component  
public class HelloBean {  
  
...  

κ·Έλ ‡λ‹€λ©΄ μ’€ 더 μžμ„Ένžˆ 각각의 μŠ€μ½”ν”„μ— λŒ€ν•΄ μ•Œμ•„λ³΄μž.

ν”„λ‘œν† νƒ€μž… μŠ€μ½”ν”„λŠ” κ°„λ‹¨νžˆ 말해 싱글톀이 μ•„λ‹Œ Bean을 μ˜λ―Έν•œλ‹€. μ˜μ‘΄μ„±μ΄ μ£Όμž…λ  λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€κ°€ μƒκ²¨λ‚˜λŠ” 것이닀. μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” μ˜μ‘΄μ„± μ£Όμž… 및 μ΄ˆκΈ°ν™”κΉŒμ§€λ§Œμ„ λ‹΄λ‹Ήν•˜κ³  더 이상 이 Bean을 κ΄€λ¦¬ν•˜μ§€ μ•ŠλŠ”λ‹€. λ”°λΌμ„œ μœ„ 생λͺ…μ£ΌκΈ°μ—μ„œ μ„€λͺ…ν–ˆλ˜ Destroy λ©”μ†Œλ“œ 등도 ν˜ΈμΆœλ˜μ§€ μ•ŠλŠ”λ‹€.

이런 경우λ₯Ό μ‘°μ‹¬ν•˜μž. 싱글톀 Bean이 ν”„λ‘œν† νƒ€μž… Bean을 μ£Όμž…λ°›λŠ” 경우 싱글톀 Bean μž…μž₯μ—μ„œλŠ” ν”„λ‘œν† νƒ€μž… Bean을 μ£Όμž…λ°›μ•„ 레퍼런슀λ₯Ό κ°€μ§€κ³  μžˆλŠ” κ²ƒμ΄λ―€λ‘œ κ·Έ λ©”μ†Œλ“œκ°€ λΆˆλ¦΄λ•Œλ§ˆλ‹€ μƒˆλ‘œ μƒμ„±λ˜μ§€λŠ” μ•ŠλŠ”λ‹€λŠ” 점이닀. λ‹€μ‹œ λ§ν•˜μžλ©΄ μ‚¬μš©ν• λ•Œλ§ˆλ‹€ μƒμ„±λ˜μ§ˆ μ•ŠλŠ”λ‹€!

κ·Έλ ‡λ‹€λ©΄ 이런 λ¬Έμ œλŠ” μ–΄λ–»κ²Œ ν•΄κ²°ν•΄μ•Όν• κΉŒ? μ΄ˆλ°˜μ— μ„€λͺ…ν–ˆλ˜ Providerλ₯Ό μ΄μš©ν•΄μ„œ Bean을 κ·Έλ•Œκ·Έλ•Œ μ£Όμž…λ°›λŠ”λ‹€λ©΄ 해결은 κ°€λŠ₯ν•  것이닀.

public int logic() {  
      PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);  
  
...  

ν•˜μ§€λ§Œ 더 κΉ”λ”ν•œ 방법은 μ—†μ„κΉŒ? μœ„ 방법은 μ»¨ν…μŠ€νŠΈλ₯Ό κ°€μ Έμ™€μ•Όν•˜κ³  그리 μ΄μ˜μ§€ μ•Šμ€ APIλ₯Ό 계속 ν˜ΈμΆœν•΄μ•Ό ν•œλ‹€.

Dependancy Lookup을 μ΄μš©ν•˜λ©΄ κ°€λŠ₯ν•˜λ‹€. μ˜μ‘΄κ΄€κ³„λ₯Ό μ™ΈλΆ€μ—μ„œ μ£Όμž…(DI) λ°›λŠ”κ²Œ μ•„λ‹ˆλΌ μ΄λ ‡κ²Œ 직접 ν•„μš”ν•œ μ˜μ‘΄κ΄€κ³„λ₯Ό μ°ΎλŠ” 것을 Dependency Lookup (DL) μ˜μ‘΄κ΄€κ³„ μ‘°νšŒλΌν•œλ‹€.

μŠ€ν”„λ§μ—μ„œ μ§€μ •ν•œ Bean을 μ»¨ν…Œμ΄λ„ˆμ—μ„œ λŒ€μ‹  μ°Ύμ•„μ£ΌλŠ” DL μ„œλΉ„μŠ€λ₯Ό μ œκ³΅ν•˜λŠ” 것이 λ°”λ‘œ ObjectProvider이닀. μ‚¬μš©λ²•μ€ μ•„λž˜μ™€ κ°™λ‹€.

  @Autowired  
  private ObjectProvider<PrototypeBean> prototypeBeanProvider;  
  public int logic() {  
      PrototypeBean prototypeBean = prototypeBeanProvider.getObject();  
      prototypeBean.addCount();  
      int count = prototypeBean.getCount();  
      return count;  
}  

μœ„ κΈ°λŠ₯은 μŠ€ν”„λ§μ— μ˜μ‘΄μ μ΄λ‹€. μžλ°” ν‘œμ€€λ§ŒμœΌλ‘œ ν•΄κ²°ν•˜λŠ” 방법 λ˜ν•œ μ‘΄μž¬ν•œλ‹€.

//implementation 'javax.inject:javax.inject:1' gradle μΆ”κ°€ ν•„μˆ˜ @Autowired  
private Provider<PrototypeBean> provider;  
  public int logic() {  
      PrototypeBean prototypeBean = provider.get();  
      prototypeBean.addCount();  
      int count = prototypeBean.getCount();  
      return count;  
}  

μŠ€ν”„λ§μ— μ˜μ‘΄ν•˜μ§€λŠ” μ•Šμ§€λ§Œ gradle에 μ„€μ •μΆ”κ°€κ°€ ν•„μš”ν•˜κ³ , Bean을 μ°ΎλŠ”λ‹€λŠ” μžμ²΄κ°€ μŠ€ν”„λ§μ— μ˜μ‘΄μ μ΄μ§€ μ•Šλ‚˜ν•˜λŠ” 생각이 λ“ λ‹€. μ•„λ¬΄λž˜λ„ ObjectProvider의 μ‚¬μš©μ΄ κ°€μž₯ 쒋아보인닀. λŒ€λΆ€λΆ„μ€ μžλ°” ν‘œμ€€λ³΄λ‹€ μŠ€ν”„λ§μ΄ νŽΈλ¦¬ν•˜κ²Œ κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κ³  μžˆλ‹€.

DL은 ν”„λ‘œν† νƒ€μž… Bean의 μ£Όμž… 이외에도 쓸일이 μžˆμ„ 수 μžˆμœΌλ‹ˆ 잘 μ •λ¦¬ν•΄λ‘μž.

μ΄λ²ˆμ—λŠ” ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•˜λŠ” 방법을 μ•Œμ•„λ³΄μž.
ν΄λΌμ΄μ–ΈνŠΈλŠ” μ‹€μ œ Beanκ³Ό λ˜‘κ°™μ€ Proxy Bean을 μ£Όμž…λ°›κ³  λ‹€ν˜•μ„±μ„ μ΄μš©ν•΄μ„œ μ‹€μ œ Bean이 ν˜ΈμΆœλ˜μ–΄ 각각 μ‹€μ œλ‘œλŠ” μ•Œλ§žμ€ Bean을 μ‚¬μš©ν•  수 μžˆλŠ” 방식이닀.

λ‚΄λΆ€ ꡬ쑰λ₯Ό μ‚΄νŽ΄λ³΄λ©΄ μ΄λ²ˆμ—λ„ CGLIBλ₯Ό 톡해 Proxy 객체가 μƒμ„±λ˜λŠ” 방식이닀. μœ„μž„ 둜직이 μžλ™μœΌλ‘œ κ΅¬ν˜„λ˜λ©° μ‹€μ œ Bean을 μ°ΎλŠ” 과정은 ν˜ΈμΆœκΉŒμ§€ μ§€μ—°λœλ‹€.

 @Component  
  @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)  
  public class MyLogger {  
  }  

μ €λ ‡κ²Œ μ„€μ •ν•˜λ©΄ MyLoggerλ₯Ό μ‚¬μš©ν•˜λŠ” λΆ€λΆ„μ—λŠ” ν”„λ‘μ‹œ 객체가 μžλ™μœΌλ‘œ μ£Όμž…λ˜λŠ” 것이닀.