빈 μŠ€μ½”ν”„

2025. 3. 25. 23:14Β·πŸ“ Spring/Lecture

μΈν”„λŸ° μŠ€ν”„λ§ 핡심 원리 κΈ°λ³ΈνŽΈμ„ μ •λ¦¬ν•œ κΈ€μž…λ‹ˆλ‹€.

 

 

μŠ€μ½”ν”„ : 빈이 μ‘΄μž¬ν•  수 μžˆλŠ” λ²”μœ„

 

μŠ€ν”„λ§ 지원 μŠ€μ½”ν”„

싱글톀 : κΈ°λ³Έ μŠ€μ½”ν”„, μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ˜ μ‹œμž‘κ³Ό μ’…λ£ŒκΉŒμ§€ μœ μ§€λ˜λŠ” κ°€μž₯ 넓은 λ²”μœ„μ˜ μŠ€μ½”ν”„

ν”„λ‘œν† νƒ€μž… : μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” ν”„λ‘œν† νƒ€μž… 빈의 생성과 μ˜μ‘΄κ΄€κ³„ μ£Όμž…κΉŒμ§€λ§Œ κ΄€μ—¬ν•˜κ³  λ”λŠ” κ΄€λ¦¬ν•˜μ§€ μ•ŠλŠ” 맀우 짧은 λ²”μœ„μ˜ μŠ€μ½”ν”„

μ›Ή κ΄€λ ¨

  • request : μ›Ή μš”μ²­μ΄ λ“€μ–΄μ˜€κ³  λ‚˜κ°ˆ λ•ŒκΉŒμ§€ μœ μ§€λ˜λŠ” μŠ€μ½”ν”„
  • session : μ›Ή μ„Έμ…˜μ΄ μƒμ„±λ˜κ³  μ’…λ£Œλ  λ•ŒκΉŒμ§€ μœ μ§€λ˜λŠ” μŠ€μ½”ν”„
  • application : μ›Ήμ˜ μ„œλΈ”λ¦Ώ μ»¨ν…μŠ€νŠΈμ™€ 같은 λ²”μœ„λ‘œ μœ μ§€λ˜λŠ” μŠ€μ½”ν”„
//μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ” μžλ™ 등둝
@Scope("prototype")
@Component
public class HelloBean {}

//μˆ˜λ™ 등둝
@Scope("prototype")
@Bean
PrototypeBean HelloBean() {
  return new HelloBean();
}

 

 

싱글톀 μŠ€μ½”ν”„

μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” 항상 같은 μΈμŠ€ν„΄μŠ€μ˜ μŠ€ν”„λ§ λΉˆμ„ λ°˜ν™˜

SingletonBean.init
singletonBean1 = ~.SingletonTest$SingletonBean@12bcd0c0
singletonBean2 = ~.SingletonTest$SingletonBean@12bcd0c0
SingletonBean.destory

 

 

 

ν”„λ‘œν† νƒ€μž… μŠ€μ½”ν”„

μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” 항상 μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•΄μ„œ λ°˜ν™˜

ν”„λ‘œν† νƒ€μž… μŠ€μ½”ν”„μ˜ λΉˆμ„ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ— μš”μ²­ → μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” ν”„λ‘œν† νƒ€μž… 빈 생성, ν•„μš”ν•œ μ˜μ‘΄κ΄€κ³„ μ£Όμž…, μ΄ˆκΈ°ν™”

이후 μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλŠ” μƒμ„±λœ ν”„λ‘œν† νƒ€μž… λΉˆμ„ 관리 X (ν”„λ‘œν† νƒ€μž… λΉˆμ„ 받은 ν΄λΌμ΄μ–ΈνŠΈκ°€ 관리) → μ’…λ£Œ λ©”μ„œλ“œ(@PreDestory) μžλ™ 호좜 X

find prototypeBean1
SingletonBean.init
find prototypeBean2
SingletonBean.init
prototypeBean1 = com.sunny.item_service.scope.PrototypeTest$PrototypeBean@12bcd0c0
prototypeBean2 = com.sunny.item_service.scope.PrototypeTest$PrototypeBean@4879f0f2

 

 

싱글톀 빈과 ν”„ν† νƒ€μž… λΉˆμ„ ν•¨κ»˜

public class SingletonWithPrototypeTest {
  @Test
  void singletonClientUsePrototype() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);

    ClientBean clientBean1 = ac.getBean(ClientBean.class);
    int count1 = clientBean1.logic();
    assertThat(count1).isEqualTo(1);

    ClientBean clientBean2 = ac.getBean(ClientBean.class);
    int count2 = clientBean2.logic();
    assertThat(count2).isEqualTo(2);
  }

  @Scope("singleton")
  static class ClientBean {
    private final PrototypeBean prototypeBean; //생성 μ‹œμ μ— μ£Όμž…

    @Autowired
    public ClientBean(PrototypeBean prototypeBean) { this.prototypeBean = prototypeBean; }

    public int logic() {
      prototypeBean.addCount();
      return prototypeBean.getCount();
    }
  }

  @Scope("prototype")
  static class PrototypeBean {
    private int count = 0;
    public void addCount() { count++; }
    public int getCount() { return count; }

    @PostConstruct
    public void init() { System.out.println("PrototypeBean.init = " + this); }

    @PreDestroy
    public void destroy() { System.out.println("PrototypeBean.destroy"); }
  }
}

싱글톀 λΉˆμ€ 생성 μ‹œμ μ—λ§Œ μ˜μ‘΄κ΄€κ³„ μ£Όμž…μ„ λ°›κΈ° λ•Œλ¬Έμ—,

ν”„λ‘œν† νƒ€μž… 빈이 μƒˆλ‘œ μƒμ„±λ˜κΈ°λŠ” ν•˜μ§€λ§Œ 싱글톀 빈과 ν•¨κ»˜ 계속 μœ μ§€λ˜λŠ” 것이 문제

ν”„λ‘œν† νƒ€μž… λΉˆμ„ μ£Όμž… μ‹œμ μ—λ§Œ μƒˆλ‘œ μƒμ„±ν•˜λŠ”κ²Œ μ•„λ‹ˆλΌ μ‚¬μš©ν•  λ•Œλ§ˆλ‹€ μƒˆλ‘œ μƒμ„±ν•΄μ„œ μ‚¬μš©ν•˜κ³  싢을 땐,

1. 싱글톀 빈이 ν”„λ‘œν† νƒ€μž…μ„ μ‚¬μš©ν•  λ•Œλ§ˆλ‹€ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ— μƒˆλ‘œ μš”μ²­

public class SingletonWithPrototypeTest1 {
  @Test
  void singletonClientUsePrototype() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);

    ClientBean clientBean1 = ac.getBean(ClientBean.class);
    int count1 = clientBean1.logic();
    assertThat(count1).isEqualTo(1);

    ClientBean clientBean2 = ac.getBean(ClientBean.class);
    int count2 = clientBean2.logic();
    assertThat(count2).isEqualTo(1);
  }

  @Scope("singleton")
  static class ClientBean {
    @Autowired
    private ObjectProvider<PrototypeBean> prototypeBeanProvider;

    public int logic() {
      PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
      prototypeBean.addCount();
      return prototypeBean.getCount();
    }
  }

  @Scope("prototype")
  static class PrototypeBean {...}
}

2. ObjectFactory, ObjectProvider (κΈ°λŠ₯ 차이)

build.gradle μΆ”κ°€
dependencies {
  implementation 'jakarta.inject:jakarta.inject-api:2.0.1'
}
import jakarta.inject.Provider;

public class SingletonWithPrototypeTest {
  @Test
  void singletonClientUsePrototype() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);

    ClientBean clientBean1 = ac.getBean(ClientBean.class);
    int count1 = clientBean1.logic();
    assertThat(count1).isEqualTo(1);

    ClientBean clientBean2 = ac.getBean(ClientBean.class);
    int count2 = clientBean2.logic();
    assertThat(count2).isEqualTo(1);
  }

  @Scope("singleton")
  static class ClientBean {
    @Autowired
    private Provider<PrototypeBean> prototypeBeanProvider;

    public int logic() {
      PrototypeBean prototypeBean = prototypeBeanProvider.get();
      prototypeBean.addCount();
      return prototypeBean.getCount();
    }
  }

  @Scope("prototype")
  static class PrototypeBean { ... }
}

3. JSR-330 Provider

Provider의 get() ν˜ΈμΆœν•˜λ©΄ λ‚΄λΆ€μ—μ„œλŠ” μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆλ₯Ό 톡해 ν•΄λ‹Ή λΉˆμ„ μ°Ύμ•„μ„œ λ°˜ν™˜ (DL)

JSR-330 Provider : μ½”λ“œλ₯Ό μŠ€ν”„λ§μ΄ μ•„λ‹Œ λ‹€λ₯Έ μ»¨ν…Œμ΄λ„ˆμ—μ„œλ„ μ‚¬μš©ν•  수 μžˆμ–΄μ•Ό ν•œλ‹€λ©΄ μ‚¬μš©
ObjectProvider : DL을 μœ„ν•œ 편의 κΈ°λŠ₯을 많이 제곡, μŠ€ν”„λ§ μ™Έ λ³„λ„μ˜ μ˜μ‘΄κ΄€κ³„ μΆ”κ°€κ°€ ν•„μš”μ—†κΈ° λ•Œλ¬Έμ— 편리
νŠΉλ³„νžˆ λ‹€λ₯Έ μ»¨ν…Œμ΄λ„ˆλ₯Ό μ‚¬μš©ν•  일이 μ—†λ‹€λ©΄, μŠ€ν”„λ§μ΄ μ œκ³΅ν•˜λŠ” κΈ°λŠ₯을 μ‚¬μš©ν•˜μž! (ObjectProvider)

 

 

맀번 μ‚¬μš©ν•  λ•Œλ§ˆλ‹€ μ˜μ‘΄κ΄€κ³„ μ£Όμž…μ΄ μ™„λ£Œλœ μƒˆλ‘œμš΄ 객체가 ν•„μš”ν•  λ•Œ ν”„λ‘œν† νƒ€μž… 빈 μ‚¬μš©
μ‹€λ¬΄μ—μ„œλŠ” 싱글톀 빈으둜 λŒ€λΆ€λΆ„μ˜ 문제 ν•΄κ²° κ°€λŠ₯

 

 

 

μ›Ή μŠ€μ½”ν”„

μ›Ή ν™˜κ²½μ—μ„œλ§Œ λ™μž‘, μŠ€ν”„λ§μ΄ ν•΄λ‹Ή μŠ€μ½”ν”„μ˜ μ’…λ£Œμ‹œμ κΉŒμ§€ 관리 → μ’…λ£Œ λ©”μ„œλ“œ 호좜

  • request : HTTP μš”μ²­ ν•˜λ‚˜κ°€ λ“€μ–΄μ˜€κ³  λ‚˜κ°ˆ λ•ŒκΉŒμ§€ μœ μ§€λ˜λŠ” μŠ€μ½”ν”„, 각각의 HTTP μš”μ²­λ§ˆλ‹€ λ³„λ„μ˜ 빈 μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ˜κ³  κ΄€λ¦¬λœλ‹€
  • session : HTTP Sessionκ³Ό λ™μΌν•œ 생λͺ…μ£ΌκΈ°λ₯Ό κ°€μ§€λŠ” μŠ€μ½”ν”„
  • application : μ„œλΈ”λ¦Ώ μ»¨ν…μŠ€νŠΈ(ServletContext)와 λ™μΌν•œ 생λͺ…μ£ΌκΈ°λ₯Ό κ°€μ§€λŠ” μŠ€μ½”ν”„
  • websocket : μ›Ή μ†ŒμΌ“κ³Ό λ™μΌν•œ 생λͺ…μ£ΌκΈ°λ₯Ό κ°€μ§€λŠ” μŠ€μ½”ν”„
build.gradle μΆ”κ°€
dependencies {
  //web 라이브러리 μΆ”κ°€
  implementation 'org.springframework.boot:spring-boot-starter-web'
}
@Component
@Scope(value = "request") //빈 μŠ€μ½”ν”„: HTTP μš”μ²­λ‹Ή ν•˜λ‚˜μ”© 생성, HTTP μš”μ²­μ΄ λλ‚˜λŠ” μ‹œμ μ— μ’…λ£Œ
public class MyLogger {
  private String uuid;
  private String requestURL;

  //빈이 μƒμ„±λ˜λŠ” μ‹œμ μ—λŠ” requestURLλ₯Ό μ•Œ 수 μ—†μœΌλ―€λ‘œ μ™ΈλΆ€μ—μ„œ setter둜 μž…λ ₯λ°›μŒ
  public void setRequestURL(String requestURL) {
    this.requestURL = requestURL;
  }

  public void log(String message) {
    System.out.println("[" + uuid + "][" + requestURL + "] " + message);
  }

  @PostConstruct
  public void init() {
    uuid = UUID.randomUUID().toString(); //HTTP μš”μ²­λ‹Ή ν•˜λ‚˜μ”© 생성
    System.out.println("[" + uuid + "] request scope bean create: " + this);
  }

  @PreDestroy
  public void close() {
    System.out.println("[" + uuid + "] request scope bean close: " + this);
  }
}
@Controller
@RequiredArgsConstructor
public class LogDemoController {
  private final LogDemoService logDemoService;
  private final MyLogger myLogger;

  @RequestMapping("log-demo")
  @ResponseBody
  public String logDemo(HttpServletRequest request) {
    String requestURL = request.getRequestURL().toString();
    myLogger.setRequestURL(requestURL);

    myLogger.log("controller test");
    logDemoService.logic("testId");

    return "ok";
  }
}
@Service
@RequiredArgsConstructor
public class LogDemoService {
  private final MyLogger myLogger;
  
  public void logic(String id) {
    myLogger.log("service id = " + id);
  }
}
ERROR!
Caused by: org.springframework.beans.factory.support.ScopeNotActiveException: Error creating bean with name 'myLogger': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton

request scopeλŠ” ν•˜λ‚˜μ˜ μš”μ²­μ΄ λ“€μ–΄μ˜€κ³  λ‚˜κ°ˆ λ•ŒκΉŒμ§€ μœ μ§€λ˜λŠ” μŠ€μ½”ν”„μΈλ° λ“€μ–΄μ˜¨ μš”μ²­μ΄ μ—†μ–΄μ„œ λ°œμƒν•œ μ—λŸ¬

LogDemoController, LogDemoService μ½”λ“œ λ³€κ²½
private final MyLogger myLogger; → private final ObjectProvider<MyLogger> myLoggerProvider; λ³€κ²½
MyLogger myLogger = myLoggerProvider.getObject(); μΆ”κ°€

1. Provider (ObjectProvider) μ‚¬μš©

@Scope의 proxyMode μΆ”κ°€
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {}

2. ν”„λ‘μ‹œ 방식 : MyLogger의 κ°€μ§œ ν”„λ‘μ‹œ 클래슀λ₯Ό λ§Œλ“€μ–΄λ‘κ³  HTTP request와 상관없이 κ°€μ§œ ν”„λ‘μ‹œ 클래슀λ₯Ό λ‹€λ₯Έ λΉˆμ— 미리 μ£Όμž…

κ°€μ§œ ν”„λ‘μ‹œ κ°μ²΄λŠ” λ‚΄λΆ€μ—μ„œ μ§„μ§œ λΉˆμ„ μš”μ²­ν•˜λŠ” μœ„μž„ 둜직이 λ“€μ–΄μžˆλ‹€

마치 싱글톀을 μ‚¬μš©ν•˜λŠ” 것 κ°™μ§€λ§Œ λ‹€λ₯΄κ²Œ λ™μž‘ν•˜λ―€λ‘œ μ£Όμ˜ν•΄μ„œ μ‚¬μš©! λ¬΄λΆ„λ³„ν•œ μ‚¬μš© κΈˆμ§€!

[4cd29588-ab10-42b9-990c-4524da84f3bd] request scope bean create: ~.MyLogger@6d1ab145 [4cd29588-ab10-42b9-990c-4524da84f3bd][http://localhost:8080/log-demo] controller test
[4cd29588-ab10-42b9-990c-4524da84f3bd][http://localhost:8080/log-demo] service id = testId
[4cd29588-ab10-42b9-990c-4524da84f3bd] request scope bean close: ~.MyLogger@6d1ab145

 

λ°˜μ‘ν˜•

'πŸ“ Spring > Lecture' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

@ModelAttribute와 Model  (0) 2025.04.04
μŠ€ν”„λ§μ˜ 빈 생λͺ…μ£ΌκΈ° 콜백 지원  (2) 2025.03.24
Annotation 직접 생성  (0) 2025.03.24
쑰회 빈이 2개 이상일 λ•Œ  (0) 2025.03.24
μ˜μ‘΄κ΄€κ³„ μ£Όμž…  (0) 2025.03.24
'πŸ“ Spring/Lecture' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€
  • @ModelAttribute와 Model
  • μŠ€ν”„λ§μ˜ 빈 생λͺ…μ£ΌκΈ° 콜백 지원
  • Annotation 직접 생성
  • 쑰회 빈이 2개 이상일 λ•Œ
leee
leee
  • leee
    LEE
    leee
  • 전체
    였늘
    μ–΄μ œ
    • μΉ΄ν…Œκ³ λ¦¬
      • πŸ“ QA
        • Test
        • Lecture
      • πŸ“ SQL
        • πŸ“
      • πŸ“ Spring
        • Lecture
        • Error
      • πŸ“ Github
  • λΈ”λ‘œκ·Έ 메뉴

    • 🏠
  • 링크

  • 곡지사항

  • 인기 κΈ€

  • νƒœκ·Έ

    mysql_λ°μ΄ν„°νƒ€μž…λ³€ν™˜
    포슀트맨
    μƒμ„±μžμ£Όμž…
    shift_μ—°μ‚°μž
    API test
    μŠ€ν”„λ§ 객체지ν–₯
    COUNT(*)
    MySQL
    API Testing
    mysql_μ—°μ‚°μž
    SQL_Join
    mysql_join
    AppConfig
    mismatch
    api ν…ŒμŠ€νŠΈ
    Postman
    mysql_κΈ°κ°„_검색
    DI μ»¨ν…Œμ΄λ„ˆ
    API μžλ™ν™” ν…ŒμŠ€νŠΈ
    κΉƒν—ˆλΈŒ μΈν…”λ¦¬μ œμ΄ 연동 ν•΄μ œ
  • 졜근 λŒ“κΈ€

  • 졜근 κΈ€

  • hELLOΒ· Designed Byμ •μƒμš°.v4.10.5
leee
빈 μŠ€μ½”ν”„
μƒλ‹¨μœΌλ‘œ

ν‹°μŠ€ν† λ¦¬νˆ΄λ°”