从自定义一个作用域开始来了解SpringBean的作用域

打印 上一主题 下一主题

主题 926|帖子 926|积分 2782

你好,这里是codetrend专栏“Spring6全攻略”。
在 Spring 框架中,Bean 的作用域(Scope)定义了 Bean 实例在容器中如何创建、管理和销毁的策略。
Spring 提供了多种 Bean 作用域,每种作用域都有其特定的生命周期和适用场景。
先试试不同的 Bean Scope

下面通过一个简单的 Spring MVC Controller 示例来感受下 Bean 的作用域。
例子代码是这样的:
  1. import org.springframework.beans.factory.config.ConfigurableBeanFactory;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.context.annotation.Scope;
  5. import org.springframework.web.context.WebApplicationContext;
  6. import java.util.UUID;
  7. @Configuration
  8. public class AppConfig {
  9. <bean id="myBean"  scope="prototype">
  10.    
  11. </bean>@Bean
  12. <bean id="myBean"  scope="prototype">
  13.    
  14. </bean>@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
  15. <bean id="myBean"  scope="prototype">
  16.    
  17. </bean>public SingletonBean singletonBean() {
  18. <bean id="myBean"  scope="prototype">
  19.    
  20. </bean><bean id="myBean"  scope="prototype">
  21.    
  22. </bean>return new SingletonBean();
  23. <bean id="myBean"  scope="prototype">
  24.    
  25. </bean>}
  26. <bean id="myBean"  scope="prototype">
  27.    
  28. </bean>@Bean
  29. <bean id="myBean"  scope="prototype">
  30.    
  31. </bean>@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  32. <bean id="myBean"  scope="prototype">
  33.    
  34. </bean>public PrototypeBean prototypeBean() {
  35. <bean id="myBean"  scope="prototype">
  36.    
  37. </bean><bean id="myBean"  scope="prototype">
  38.    
  39. </bean>return new PrototypeBean();
  40. <bean id="myBean"  scope="prototype">
  41.    
  42. </bean>}
  43. <bean id="myBean"  scope="prototype">
  44.    
  45. </bean>@Bean
  46. <bean id="myBean"  scope="prototype">
  47.    
  48. </bean>@Scope(WebApplicationContext.SCOPE_SESSION)
  49. <bean id="myBean"  scope="prototype">
  50.    
  51. </bean>public SessionBean sessionBean() {
  52. <bean id="myBean"  scope="prototype">
  53.    
  54. </bean><bean id="myBean"  scope="prototype">
  55.    
  56. </bean>return new SessionBean();
  57. <bean id="myBean"  scope="prototype">
  58.    
  59. </bean>}
  60. }
  61. class SingletonBean {
  62. <bean id="myBean"  scope="prototype">
  63.    
  64. </bean>private int count = 0;
  65. <bean id="myBean"  scope="prototype">
  66.    
  67. </bean>public void increment() {
  68. <bean id="myBean"  scope="prototype">
  69.    
  70. </bean><bean id="myBean"  scope="prototype">
  71.    
  72. </bean>count++;
  73. <bean id="myBean"  scope="prototype">
  74.    
  75. </bean>}
  76. <bean id="myBean"  scope="prototype">
  77.    
  78. </bean>public int getCount() {
  79. <bean id="myBean"  scope="prototype">
  80.    
  81. </bean><bean id="myBean"  scope="prototype">
  82.    
  83. </bean>return count;
  84. <bean id="myBean"  scope="prototype">
  85.    
  86. </bean>}
  87. }
  88. class PrototypeBean {
  89. <bean id="myBean"  scope="prototype">
  90.    
  91. </bean>private String id;
  92. <bean id="myBean"  scope="prototype">
  93.    
  94. </bean>public PrototypeBean() {
  95. <bean id="myBean"  scope="prototype">
  96.    
  97. </bean><bean id="myBean"  scope="prototype">
  98.    
  99. </bean>this.id = UUID.randomUUID().toString();
  100. <bean id="myBean"  scope="prototype">
  101.    
  102. </bean>}
  103. <bean id="myBean"  scope="prototype">
  104.    
  105. </bean>public String getId() {
  106. <bean id="myBean"  scope="prototype">
  107.    
  108. </bean><bean id="myBean"  scope="prototype">
  109.    
  110. </bean>return id;
  111. <bean id="myBean"  scope="prototype">
  112.    
  113. </bean>}
  114. }
  115. class SessionBean {
  116. <bean id="myBean"  scope="prototype">
  117.    
  118. </bean>private String id;
  119. <bean id="myBean"  scope="prototype">
  120.    
  121. </bean>public SessionBean() {
  122. <bean id="myBean"  scope="prototype">
  123.    
  124. </bean><bean id="myBean"  scope="prototype">
  125.    
  126. </bean>this.id = UUID.randomUUID().toString();
  127. <bean id="myBean"  scope="prototype">
  128.    
  129. </bean>}
  130. <bean id="myBean"  scope="prototype">
  131.    
  132. </bean>public String getId() {
  133. <bean id="myBean"  scope="prototype">
  134.    
  135. </bean><bean id="myBean"  scope="prototype">
  136.    
  137. </bean>return id;
  138. <bean id="myBean"  scope="prototype">
  139.    
  140. </bean>}
  141. }
复制代码
controller 代码:
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. public class ScopeController {
  7. <bean id="myBean"  scope="prototype">
  8.    
  9. </bean>@Autowired
  10. <bean id="myBean"  scope="prototype">
  11.    
  12. </bean>private SingletonBean singletonBean;
  13. <bean id="myBean"  scope="prototype">
  14.    
  15. </bean>@Autowired
  16. <bean id="myBean"  scope="prototype">
  17.    
  18. </bean>private ApplicationContext context;
  19. <bean id="myBean"  scope="prototype">
  20.    
  21. </bean>@GetMapping("/singleton")
  22. <bean id="myBean"  scope="prototype">
  23.    
  24. </bean>public String singletonCount() {
  25. <bean id="myBean"  scope="prototype">
  26.    
  27. </bean><bean id="myBean"  scope="prototype">
  28.    
  29. </bean>singletonBean.increment();
  30. <bean id="myBean"  scope="prototype">
  31.    
  32. </bean><bean id="myBean"  scope="prototype">
  33.    
  34. </bean>return "Singleton Count: " + singletonBean.getCount();
  35. <bean id="myBean"  scope="prototype">
  36.    
  37. </bean>}
  38. <bean id="myBean"  scope="prototype">
  39.    
  40. </bean>@GetMapping("/prototype")
  41. <bean id="myBean"  scope="prototype">
  42.    
  43. </bean>public String prototypeGet() {
  44. <bean id="myBean"  scope="prototype">
  45.    
  46. </bean><bean id="myBean"  scope="prototype">
  47.    
  48. </bean>PrototypeBean prototypeBean = context.getBean(PrototypeBean.class);
  49. <bean id="myBean"  scope="prototype">
  50.    
  51. </bean><bean id="myBean"  scope="prototype">
  52.    
  53. </bean>return "Prototype ID: " + prototypeBean.getId();
  54. <bean id="myBean"  scope="prototype">
  55.    
  56. </bean>}
  57. <bean id="myBean"  scope="prototype">
  58.    
  59. </bean>@GetMapping("/session")
  60. <bean id="myBean"  scope="prototype">
  61.    
  62. </bean>public String sessionGet() {
  63. <bean id="myBean"  scope="prototype">
  64.    
  65. </bean><bean id="myBean"  scope="prototype">
  66.    
  67. </bean>SessionBean prototypeBean = context.getBean(SessionBean.class);
  68. <bean id="myBean"  scope="prototype">
  69.    
  70. </bean><bean id="myBean"  scope="prototype">
  71.    
  72. </bean>return "Session ID: " + prototypeBean.getId();
  73. <bean id="myBean"  scope="prototype">
  74.    
  75. </bean>}
  76. }
复制代码

  • Singleton(单例)的属性连续增加,也就是说访问到的SingletonBean每次都是同一个对象。访问/singleton接口的返回是这样的:
  1. 1
  2. 2
  3. 3
复制代码

  • Prototype(原型)的属性每次都是不一样的,也就是阐明id每次都是调用构造器新创建的。访问/prototype接口的返回是这样的:
  1. Prototype ID: 3ea5af10-ddce-4a89-ad3c-3f07a764f179
  2. Prototype ID: 7e6e9fe8-c0dc-423e-b282-96b7f8087dac
  3. Prototype ID: 7aca1000-484d-46e8-80f7-d444a5a04f49
复制代码

  • Session(会话)的属性同一窗口是一样的,开启无痕窗口或者其他浏览器就不一样。访问/session接口的返回是这样的:
  1. Prototype ID: 7aca1000-484d-46e8-80f7-d444a5a04f49
  2. Prototype ID: 7aca1000-484d-46e8-80f7-d444a5a04f49
  3. # 开启新的窗口后
  4. Session ID: bd22d310-29e5-4004-8555-9678c08275f0
  5. Session ID: bd22d310-29e5-4004-8555-9678c08275f0
复制代码
可以直接把样例代码复制到例子里面验证测试。这样我们就对BeanScope作用域有个直观的感受。
自定义一个 Bean Scope

接下来通过实现一个自定义作用域来感受下Bean的作用域原理。
在 Spring 框架中,除了预定义的几种作用域(如 singleton、prototype 等)外,用户还可以自定义作用域以满足特定的业务需求。
自定义作用域允许控制 Bean 的创建、缓存和销毁逻辑,以适应特定的场景,如基于特定条件的实例化策略、自定义生命周期管理等。
自定义步骤:

  • 定义作用域接口:首先,必要实现org.springframework.beans.factory.config.Scope接口,该接口定义了 Bean 作用域的基本行为。
  • 实现逻辑:在自定义的 Scope 接口实现中,必要覆盖get、remove和registerDestructionCallback方法,分别用于获取 Bean 实例、移除 Bean 实例以及注册销毁回调。
  • 注册作用域:在 Spring 配置中注册的自定义作用域,使其可被容器识别和使用。
  • 使用自定义作用域:在 Bean 定义中通过@Scope注解指定使用自定义的作用域名称。
自定义作用域实现

首先自定义作用域实现,也就是实现接口org.springframework.beans.factory.config.Scope。
  1. import org.springframework.beans.factory.ObjectFactory;
  2. import org.springframework.beans.factory.config.Scope;
  3. import java.util.concurrent.ConcurrentHashMap;
  4. import java.util.Map;
  5. import java.util.UUID;
  6. public class CustomScope implements Scope {
  7. <bean id="myBean"  scope="prototype">
  8.    
  9. </bean>public final static String CUSTOM_SCOPE_NAME = "custom";
  10. <bean id="myBean"  scope="prototype">
  11.    
  12. </bean>private final Map<String, Object> scopedObjects = new ConcurrentHashMap<>();
  13. <bean id="myBean"  scope="prototype">
  14.    
  15. </bean>private final Map<String, Runnable> destructionCallbacks = new ConcurrentHashMap<>();
  16. <bean id="myBean"  scope="prototype">
  17.    
  18. </bean>@Override
  19. <bean id="myBean"  scope="prototype">
  20.    
  21. </bean>public Object get(String name, ObjectFactory<?> objectFactory) {
  22. <bean id="myBean"  scope="prototype">
  23.    
  24. </bean><bean id="myBean"  scope="prototype">
  25.    
  26. </bean>Object scopedObject = scopedObjects.get(name);
  27. <bean id="myBean"  scope="prototype">
  28.    
  29. </bean><bean id="myBean"  scope="prototype">
  30.    
  31. </bean>if (scopedObject == null) {
  32. <bean id="myBean"  scope="prototype">
  33.    
  34. </bean><bean id="myBean"  scope="prototype">
  35.    
  36. </bean><bean id="myBean"  scope="prototype">
  37.    
  38. </bean>scopedObject = objectFactory.getObject();
  39. <bean id="myBean"  scope="prototype">
  40.    
  41. </bean><bean id="myBean"  scope="prototype">
  42.    
  43. </bean><bean id="myBean"  scope="prototype">
  44.    
  45. </bean>scopedObjects.put(name, scopedObject);
  46. <bean id="myBean"  scope="prototype">
  47.    
  48. </bean><bean id="myBean"  scope="prototype">
  49.    
  50. </bean>}
  51. <bean id="myBean"  scope="prototype">
  52.    
  53. </bean><bean id="myBean"  scope="prototype">
  54.    
  55. </bean>return scopedObject;
  56. <bean id="myBean"  scope="prototype">
  57.    
  58. </bean>}
  59. <bean id="myBean"  scope="prototype">
  60.    
  61. </bean>@Override
  62. <bean id="myBean"  scope="prototype">
  63.    
  64. </bean>public Object remove(String name) {
  65. <bean id="myBean"  scope="prototype">
  66.    
  67. </bean><bean id="myBean"  scope="prototype">
  68.    
  69. </bean>scopedObjects.remove(name);
  70. <bean id="myBean"  scope="prototype">
  71.    
  72. </bean><bean id="myBean"  scope="prototype">
  73.    
  74. </bean>Runnable callback = destructionCallbacks.remove(name);
  75. <bean id="myBean"  scope="prototype">
  76.    
  77. </bean><bean id="myBean"  scope="prototype">
  78.    
  79. </bean>if (callback != null) {
  80. <bean id="myBean"  scope="prototype">
  81.    
  82. </bean><bean id="myBean"  scope="prototype">
  83.    
  84. </bean><bean id="myBean"  scope="prototype">
  85.    
  86. </bean>callback.run();
  87. <bean id="myBean"  scope="prototype">
  88.    
  89. </bean><bean id="myBean"  scope="prototype">
  90.    
  91. </bean>}
  92. <bean id="myBean"  scope="prototype">
  93.    
  94. </bean><bean id="myBean"  scope="prototype">
  95.    
  96. </bean>return null;
  97. <bean id="myBean"  scope="prototype">
  98.    
  99. </bean>}
  100. <bean id="myBean"  scope="prototype">
  101.    
  102. </bean>@Override
  103. <bean id="myBean"  scope="prototype">
  104.    
  105. </bean>public void registerDestructionCallback(String name, Runnable callback) {
  106. <bean id="myBean"  scope="prototype">
  107.    
  108. </bean><bean id="myBean"  scope="prototype">
  109.    
  110. </bean>destructionCallbacks.put(name, callback);
  111. <bean id="myBean"  scope="prototype">
  112.    
  113. </bean>}
  114. <bean id="myBean"  scope="prototype">
  115.    
  116. </bean>@Override
  117. <bean id="myBean"  scope="prototype">
  118.    
  119. </bean>public Object resolveContextualObject(String key) {
  120. <bean id="myBean"  scope="prototype">
  121.    
  122. </bean><bean id="myBean"  scope="prototype">
  123.    
  124. </bean>// 可以根据需要实现上下文对象解析逻辑
  125. <bean id="myBean"  scope="prototype">
  126.    
  127. </bean><bean id="myBean"  scope="prototype">
  128.    
  129. </bean>return null;
  130. <bean id="myBean"  scope="prototype">
  131.    
  132. </bean>}
  133. <bean id="myBean"  scope="prototype">
  134.    
  135. </bean>@Override
  136. <bean id="myBean"  scope="prototype">
  137.    
  138. </bean>public String getConversationId() {
  139. <bean id="myBean"  scope="prototype">
  140.    
  141. </bean><bean id="myBean"  scope="prototype">
  142.    
  143. </bean>// 返回一个唯一的标识,用于区分作用域上下文
  144. <bean id="myBean"  scope="prototype">
  145.    
  146. </bean><bean id="myBean"  scope="prototype">
  147.    
  148. </bean>return UUID.randomUUID().toString();
  149. <bean id="myBean"  scope="prototype">
  150.    
  151. </bean>}
  152. }
复制代码
可以看到Scope接口其实是对Bean的全生命周期进行管理,包括获取get、缓存和销毁remove和销毁回调等逻辑。这也是作用域的核心原理。
Spring6怎么实现的Scope

这里以org.springframework.web.context.request.RequestScope 为例子来理解Spring6怎么实现BeanScope的。
得益于Spring框架的抽象和封装,这个类的实现代码并没有多少。

  • RequestScope extends AbstractRequestAttributesScope 核心实现在这个类 AbstractRequestAttributesScope。
  • get获取对象方法,其中对象的存储放在了ThreadLocal中,也就是RequestContextHolder这个类的核心。
  1. /**
  2. * 根据名称获取对象,如果当前请求属性中没有该对象,则使用对象工厂创建一个对象,并将其设置到请求属性中
  3. * 然后再次获取该对象,以便进行隐式会话属性更新。作为额外的好处,我们还允许在获取属性级别进行潜在的装饰。
  4. * 如果再次获取到的对象不为空(预期情况),则只使用该对象。如果它同时消失了,我们则返回本地创建的实例。
  5. */
  6. public Object get(String name, ObjectFactory<?> objectFactory) {
  7. <bean id="myBean"  scope="prototype">
  8.    
  9. </bean>// 获取当前请求的属性
  10. <bean id="myBean"  scope="prototype">
  11.    
  12. </bean>RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
  13. <bean id="myBean"  scope="prototype">
  14.    
  15. </bean>// 根据名称和作用域获取对象
  16. <bean id="myBean"  scope="prototype">
  17.    
  18. </bean>Object scopedObject = attributes.getAttribute(name, getScope());
  19. <bean id="myBean"  scope="prototype">
  20.    
  21. </bean>if (scopedObject == null) {
  22. <bean id="myBean"  scope="prototype">
  23.    
  24. </bean><bean id="myBean"  scope="prototype">
  25.    
  26. </bean>// 使用对象工厂创建对象
  27. <bean id="myBean"  scope="prototype">
  28.    
  29. </bean><bean id="myBean"  scope="prototype">
  30.    
  31. </bean>scopedObject = objectFactory.getObject();
  32. <bean id="myBean"  scope="prototype">
  33.    
  34. </bean><bean id="myBean"  scope="prototype">
  35.    
  36. </bean>// 将创建的对象设置到请求属性中
  37. <bean id="myBean"  scope="prototype">
  38.    
  39. </bean><bean id="myBean"  scope="prototype">
  40.    
  41. </bean>attributes.setAttribute(name, scopedObject, getScope());
  42. <bean id="myBean"  scope="prototype">
  43.    
  44. </bean><bean id="myBean"  scope="prototype">
  45.    
  46. </bean>// 再次获取对象,进行隐式会话属性更新
  47. <bean id="myBean"  scope="prototype">
  48.    
  49. </bean><bean id="myBean"  scope="prototype">
  50.    
  51. </bean>// 并允许进行潜在的装饰
  52. <bean id="myBean"  scope="prototype">
  53.    
  54. </bean><bean id="myBean"  scope="prototype">
  55.    
  56. </bean>Object retrievedObject = attributes.getAttribute(name, getScope());
  57. <bean id="myBean"  scope="prototype">
  58.    
  59. </bean><bean id="myBean"  scope="prototype">
  60.    
  61. </bean>if (retrievedObject!= null) {
  62. <bean id="myBean"  scope="prototype">
  63.    
  64. </bean><bean id="myBean"  scope="prototype">
  65.    
  66. </bean><bean id="myBean"  scope="prototype">
  67.    
  68. </bean>// 只使用再次获取到的对象(如果仍然存在,这是预期情况)
  69. <bean id="myBean"  scope="prototype">
  70.    
  71. </bean><bean id="myBean"  scope="prototype">
  72.    
  73. </bean><bean id="myBean"  scope="prototype">
  74.    
  75. </bean>// 如果它同时消失了,我们则返回本地创建的实例
  76. <bean id="myBean"  scope="prototype">
  77.    
  78. </bean><bean id="myBean"  scope="prototype">
  79.    
  80. </bean><bean id="myBean"  scope="prototype">
  81.    
  82. </bean>scopedObject = retrievedObject;
  83. <bean id="myBean"  scope="prototype">
  84.    
  85. </bean><bean id="myBean"  scope="prototype">
  86.    
  87. </bean>}
  88. <bean id="myBean"  scope="prototype">
  89.    
  90. </bean>}
  91. <bean id="myBean"  scope="prototype">
  92.    
  93. </bean>// 返回获取到的对象
  94. <bean id="myBean"  scope="prototype">
  95.    
  96. </bean>return scopedObject;
  97. }
复制代码

  • remove 方法也是差不多的。借助工具类RequestContextHolder将缓存在ThreadLocal中的对象移除。
  1. /**
  2. * 移除指定名称的对象,如果当前请求属性中存在该对象,则将其从请求属性中移除并返回该对象;否则返回 null
  3. */
  4. public Object remove(String name) {
  5. <bean id="myBean"  scope="prototype">
  6.    
  7. </bean>// 获取当前请求的属性
  8. <bean id="myBean"  scope="prototype">
  9.    
  10. </bean>RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
  11. <bean id="myBean"  scope="prototype">
  12.    
  13. </bean>// 根据名称和作用域获取对象
  14. <bean id="myBean"  scope="prototype">
  15.    
  16. </bean>Object scopedObject = attributes.getAttribute(name, getScope());
  17. <bean id="myBean"  scope="prototype">
  18.    
  19. </bean>if (scopedObject!= null) {
  20. <bean id="myBean"  scope="prototype">
  21.    
  22. </bean><bean id="myBean"  scope="prototype">
  23.    
  24. </bean>// 将该对象从请求属性中移除
  25. <bean id="myBean"  scope="prototype">
  26.    
  27. </bean><bean id="myBean"  scope="prototype">
  28.    
  29. </bean>attributes.removeAttribute(name, getScope());
  30. <bean id="myBean"  scope="prototype">
  31.    
  32. </bean><bean id="myBean"  scope="prototype">
  33.    
  34. </bean>// 返回移除的对象
  35. <bean id="myBean"  scope="prototype">
  36.    
  37. </bean><bean id="myBean"  scope="prototype">
  38.    
  39. </bean>return scopedObject;
  40. <bean id="myBean"  scope="prototype">
  41.    
  42. </bean>} else {
  43. <bean id="myBean"  scope="prototype">
  44.    
  45. </bean><bean id="myBean"  scope="prototype">
  46.    
  47. </bean>// 返回 null
  48. <bean id="myBean"  scope="prototype">
  49.    
  50. </bean><bean id="myBean"  scope="prototype">
  51.    
  52. </bean>return null;
  53. <bean id="myBean"  scope="prototype">
  54.    
  55. </bean>}
  56. }
复制代码
注册自定义作用域

注册作用域,必要通过BeanFactory的registerScope方法进行注册。
  1. import org.springframework.beans.BeansException;
  2. import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
  3. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  4. import org.springframework.stereotype.Component;
  5. @Component
  6. public class ScopeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
  7. <bean id="myBean"  scope="prototype">
  8.    
  9. </bean>@Override
  10. <bean id="myBean"  scope="prototype">
  11.    
  12. </bean>public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  13. <bean id="myBean"  scope="prototype">
  14.    
  15. </bean><bean id="myBean"  scope="prototype">
  16.    
  17. </bean>beanFactory.registerScope(CustomScope.CUSTOM_SCOPE_NAME, new CustomScope());
  18. <bean id="myBean"  scope="prototype">
  19.    
  20. </bean>}
  21. }
复制代码
验证自定义作用域结果

将Bean注册到Spring容器中,并使用自定义作用域。
  1. public class MyScopeBean {
  2. <bean id="myBean"  scope="prototype">
  3.    
  4. </bean>private String id;
  5. <bean id="myBean"  scope="prototype">
  6.    
  7. </bean>public MyScopeBean() {
  8. <bean id="myBean"  scope="prototype">
  9.    
  10. </bean><bean id="myBean"  scope="prototype">
  11.    
  12. </bean>this.id = UUID.randomUUID().toString();
  13. <bean id="myBean"  scope="prototype">
  14.    
  15. </bean>}
  16. <bean id="myBean"  scope="prototype">
  17.    
  18. </bean>public String getId() {
  19. <bean id="myBean"  scope="prototype">
  20.    
  21. </bean><bean id="myBean"  scope="prototype">
  22.    
  23. </bean>return id;
  24. <bean id="myBean"  scope="prototype">
  25.    
  26. </bean>}
  27. }
  28. import org.springframework.context.annotation.Bean;
  29. import org.springframework.context.annotation.Configuration;
  30. import org.springframework.context.annotation.Scope;
  31. @Configuration
  32. public class AppScopeConfig {
  33. <bean id="myBean"  scope="prototype">
  34.    
  35. </bean>@Bean
  36. <bean id="myBean"  scope="prototype">
  37.    
  38. </bean>@Scope(CustomScope.CUSTOM_SCOPE_NAME)
  39. <bean id="myBean"  scope="prototype">
  40.    
  41. </bean>public MyScopeBean myBean() {
  42. <bean id="myBean"  scope="prototype">
  43.    
  44. </bean><bean id="myBean"  scope="prototype">
  45.    
  46. </bean>return new MyScopeBean();
  47. <bean id="myBean"  scope="prototype">
  48.    
  49. </bean>}
  50. }
复制代码
新建一个Controller,访问/customScope接口,返回自定义作用域的Bean实例。
  1. @RestController
  2. public class CustomScopeController {
  3. <bean id="myBean"  scope="prototype">
  4.    
  5. </bean>@Autowired
  6. <bean id="myBean"  scope="prototype">
  7.    
  8. </bean>private ApplicationContext context;
  9. <bean id="myBean"  scope="prototype">
  10.    
  11. </bean>@GetMapping("/customScope")
  12. <bean id="myBean"  scope="prototype">
  13.    
  14. </bean>public String customScope() {
  15. <bean id="myBean"  scope="prototype">
  16.    
  17. </bean><bean id="myBean"  scope="prototype">
  18.    
  19. </bean>MyScopeBean prototypeBean = context.getBean(MyScopeBean.class);
  20. <bean id="myBean"  scope="prototype">
  21.    
  22. </bean><bean id="myBean"  scope="prototype">
  23.    
  24. </bean>return "Prototype ID: " + prototypeBean.getId();
  25. <bean id="myBean"  scope="prototype">
  26.    
  27. </bean>}
  28. }
复制代码
访问的结果输出如下:
  1. Session ID: bd22d310-29e5-4004-8555-9678c08275f0
  2. Session ID: bd22d310-29e5-4004-8555-9678c08275f0
  3. Session ID: bd22d310-29e5-4004-8555-9678c08275f0
复制代码
因为对象全局缓存到了一个MapscopedObjects,所以可以看到这个自定义作用域结果和单例模式基本同等的。
Bean Scope 的分类

Scope描述singleton(Default) 将单个 bean 定义作用域限定为 Spring IoC 容器中的单个对象实例。prototype将单个 bean 定义作用域限定为恣意数量的对象实例。request将单个 bean 定义作用域限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的一个基于单个 bean 定义创建的 bean 实例。仅在 Web-aware Spring ApplicationContext 上下文中有效。session将单个 bean 定义作用域限定为 HTTP Session 的生命周期。仅在 Web-aware Spring ApplicationContext 上下文中有效。application将单个 bean 定义作用域限定为 ServletContext 的生命周期。仅在 Web-aware Spring ApplicationContext 上下文中有效。websocket将单个 bean 定义作用域限定为 WebSocket 的生命周期。仅在 Web-aware Spring ApplicationContext 上下文中有效。其中singleton、prototype是比力常用的数据。
Bean Scope 的使用

可以通过在Spring的配置文件(如XML配置文件或Java注解)中指定@Scope注解或元素的scope属性来定义Bean的Scope。
其中@Scope注解可以是自定义的值或者如下常量:

  • ConfigurableBeanFactory.SCOPE_PROTOTYPE
  • ConfigurableBeanFactory.SCOPE_SINGLETON
  • org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST
  • org.springframework.web.context.WebApplicationContext.SCOPE_SESSION
其中ConfigurableBeanFactory.SCOPE_PROTOTYPE是默认值。
例如:
  1. import org.springframework.context.annotation.Scope;
  2. import org.springframework.stereotype.Component;
  3. import org.springframework.beans.factory.config.ConfigurableBeanFactory;
  4. @Component
  5. @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
  6. public class MyPrototypeBean {
  7. <bean id="myBean"  scope="prototype">
  8.    
  9. </bean>// Bean内容
  10. }
复制代码
或者使用XML配置:
  1. <bean id="myBean"  scope="prototype">
  2.    
  3. </bean>
复制代码
选择合适的Bean Scope取决于应用步伐的需求。
为什么设计 Bean Scope

Spring 框架设计 Bean 作用域(Scope)的原因主要是为了提供灵活性和资源管理本事,以适应不同应用场景的需求。
不同的 Bean 作用域会影响 Bean 的生命周期、创建方式和在容器中的共享程度,从而影响应用的性能、内存占用和并发处置惩罚本事。
以下是 Spring 提供 Bean 作用域设计背后的主要原因:

  • 资源优化:通过作用域设计,Spring 能够根据业务场景高效管理 Bean 的创建与销毁。例如,单例(Singleton)模式可以减少频繁创建实例的开销,原型(Prototype)模式则确保每次请求都得到新的实例,避免了共享状态问题。
  • 并发处置惩罚:对于 Web 应用,特定作用域如请求(Request)和会话(Session)使得每个用户请求或会话都有独立的 Bean 实例,办理了并发用户数据隔离的问题,提高了应用的线程安全。
  • 生命周期管理:不同的作用域允许开发者控制 Bean 的生命周期,比如通过自定义作用域实现复杂的生命周期管理逻辑。Spring 容器在 Bean 的创建、初始化、销毁等关键时候调用生命周期回调方法,增加了灵活性。
  • 可测试性:通过作用域的设计,特别是原型模式,可以更容易地创建独立的测试环境,因为每次测试都能得到全新的实例,减少了测试间状态干扰。
  • 扩展性:Spring 允许开发者自定义作用域,为特定的业务需求或架构设计提供定制化的 Bean 管理方式,增强了框架的扩展性和适应性。
  • 内存管理:合理使用作用域可以减少内存消耗,例如,原型模式避免了单例 Bean 累积大量状态导致的内存泄漏风险,而请求作用域则确保请求结束后自动清算资源。
单例 bean 里面注入了原型 bean

当单例 Bean 中注入原型(Prototype)Bean 时,会出现一个问题:

  • 单例 Bean 在整个应用生命周期中只创建一次。
  • 而原型 Bean 本应每次请求时创建新实例。
  • 但直接注入到单例 Bean 中时,现实上只会注入一次原型 Bean 的实例。
  • 后续对该原型 Bean 的使用都将复用初次注入的同一个实例,这可能并不符合预期。
以下demo可以复现这种情况。
SpringBean的配置:
  1. import org.springframework.beans.factory.config.ConfigurableBeanFactory;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Scope;@Configurationpublic class FaultAppConfig {<bean id="myBean"  scope="prototype">
  2.    
  3. </bean>@Bean<bean id="myBean"  scope="prototype">
  4.    
  5. </bean>@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)<bean id="myBean"  scope="prototype">
  6.    
  7. </bean>public PrototypeInjectBean prototypeInjectBean() {<bean id="myBean"  scope="prototype">
  8.    
  9. </bean><bean id="myBean"  scope="prototype">
  10.    
  11. </bean>return new PrototypeInjectBean();<bean id="myBean"  scope="prototype">
  12.    
  13. </bean>}}
复制代码
单例SpringBean:
  1. import java.util.UUID;public class PrototypeInjectBean {<bean id="myBean"  scope="prototype">
  2.    
  3. </bean>private String id;<bean id="myBean"  scope="prototype">
  4.    
  5. </bean>public PrototypeInjectBean() {<bean id="myBean"  scope="prototype">
  6.    
  7. </bean><bean id="myBean"  scope="prototype">
  8.    
  9. </bean>this.id = UUID.randomUUID().toString();<bean id="myBean"  scope="prototype">
  10.    
  11. </bean>}<bean id="myBean"  scope="prototype">
  12.    
  13. </bean>public String getId() {<bean id="myBean"  scope="prototype">
  14.    
  15. </bean><bean id="myBean"  scope="prototype">
  16.    
  17. </bean>return id;<bean id="myBean"  scope="prototype">
  18.    
  19. </bean>}}
复制代码
测试代码如下:
  1. import org.springframework.beans.factory.ObjectProvider;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Lookup;import org.springframework.beans.factory.config.ConfigurableBeanFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.Scope;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)public class PrototypeFaultController {<bean id="myBean"  scope="prototype">
  2.    
  3. </bean>final private PrototypeInjectBean prototypeInjectBean;<bean id="myBean"  scope="prototype">
  4.    
  5. </bean>protected PrototypeFaultController(PrototypeInjectBean prototypeInjectBean) {<bean id="myBean"  scope="prototype">
  6.    
  7. </bean><bean id="myBean"  scope="prototype">
  8.    
  9. </bean>this.prototypeInjectBean = prototypeInjectBean;<bean id="myBean"  scope="prototype">
  10.    
  11. </bean>}<bean id="myBean"  scope="prototype">
  12.    
  13. </bean>/**<bean id="myBean"  scope="prototype">
  14.    
  15. </bean> * 原型作用域失效,每次返回同一个id<bean id="myBean"  scope="prototype">
  16.    
  17. </bean> * @return<bean id="myBean"  scope="prototype">
  18.    
  19. </bean> */<bean id="myBean"  scope="prototype">
  20.    
  21. </bean>@GetMapping("/prototypeDemo1")<bean id="myBean"  scope="prototype">
  22.    
  23. </bean>public String prototypeDemo1() {<bean id="myBean"  scope="prototype">
  24.    
  25. </bean><bean id="myBean"  scope="prototype">
  26.    
  27. </bean>return "Prototype ID: " + prototypeInjectBean.getId();<bean id="myBean"  scope="prototype">
  28.    
  29. </bean>}}
复制代码
在不重启应用或者垃圾接纳的情况下,访问接口 /prototypeDemo1 原型 Bean 的id值始终是相同的。
  1. Prototype ID: 11893ed8-608c-452b-b2e6-82bc70b5cf97
  2. Prototype ID: 11893ed8-608c-452b-b2e6-82bc70b5cf97
  3. Prototype ID: 11893ed8-608c-452b-b2e6-82bc70b5cf97
复制代码
那这种常用的使用场景遇到了该怎么办理呢?别急,Spring早已经给出了几种办理办法。
通过完善上面的测试代码给出3中办理方法。
修改完善后的代码如下:
  1. import org.springframework.beans.factory.ObjectProvider;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Lookup;import org.springframework.beans.factory.config.ConfigurableBeanFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.Scope;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)public abstract class PrototypeFaultController {<bean id="myBean"  scope="prototype">
  2.    
  3. </bean>@Autowired<bean id="myBean"  scope="prototype">
  4.    
  5. </bean>private ApplicationContext context;<bean id="myBean"  scope="prototype">
  6.    
  7. </bean>@Autowired<bean id="myBean"  scope="prototype">
  8.    
  9. </bean>private ObjectProvider prototypeBeanProvider;<bean id="myBean"  scope="prototype">
  10.    
  11. </bean>final private PrototypeInjectBean prototypeInjectBean;<bean id="myBean"  scope="prototype">
  12.    
  13. </bean>protected PrototypeFaultController(PrototypeInjectBean prototypeInjectBean) {<bean id="myBean"  scope="prototype">
  14.    
  15. </bean><bean id="myBean"  scope="prototype">
  16.    
  17. </bean>this.prototypeInjectBean = prototypeInjectBean;<bean id="myBean"  scope="prototype">
  18.    
  19. </bean>}<bean id="myBean"  scope="prototype">
  20.    
  21. </bean>/**<bean id="myBean"  scope="prototype">
  22.    
  23. </bean> * 原型作用域失效,每次返回同一个id<bean id="myBean"  scope="prototype">
  24.    
  25. </bean> * @return<bean id="myBean"  scope="prototype">
  26.    
  27. </bean> */<bean id="myBean"  scope="prototype">
  28.    
  29. </bean>@GetMapping("/prototypeDemo1")<bean id="myBean"  scope="prototype">
  30.    
  31. </bean>public String prototypeDemo1() {<bean id="myBean"  scope="prototype">
  32.    
  33. </bean><bean id="myBean"  scope="prototype">
  34.    
  35. </bean>return "Prototype ID: " + prototypeInjectBean.getId();<bean id="myBean"  scope="prototype">
  36.    
  37. </bean>}<bean id="myBean"  scope="prototype">
  38.    
  39. </bean>/**<bean id="myBean"  scope="prototype">
  40.    
  41. </bean> * 使用实例工厂方法注入获取原型Bean,每次返回不同id<bean id="myBean"  scope="prototype">
  42.    
  43. </bean> * @return<bean id="myBean"  scope="prototype">
  44.    
  45. </bean> */<bean id="myBean"  scope="prototype">
  46.    
  47. </bean>@GetMapping("/prototypeDemo2")<bean id="myBean"  scope="prototype">
  48.    
  49. </bean>public String prototypeDemo2() {<bean id="myBean"  scope="prototype">
  50.    
  51. </bean><bean id="myBean"  scope="prototype">
  52.    
  53. </bean>PrototypeInjectBean prototypeBean = context.getBean(PrototypeInjectBean.class);<bean id="myBean"  scope="prototype">
  54.    
  55. </bean><bean id="myBean"  scope="prototype">
  56.    
  57. </bean>return "Prototype ID: " + prototypeBean.getId();<bean id="myBean"  scope="prototype">
  58.    
  59. </bean>}<bean id="myBean"  scope="prototype">
  60.    
  61. </bean>/**<bean id="myBean"  scope="prototype">
  62.    
  63. </bean> * Spring 提供了`ObjectProvider`接口(继承自`Provider`接口),它允许延迟查找和实例化 Bean,非常适合在单例 Bean 中按需获取原型 Bean 的新实例。<bean id="myBean"  scope="prototype">
  64.    
  65. </bean> * @return<bean id="myBean"  scope="prototype">
  66.    
  67. </bean> */<bean id="myBean"  scope="prototype">
  68.    
  69. </bean>@GetMapping("/prototypeDemo4")<bean id="myBean"  scope="prototype">
  70.    
  71. </bean>public String prototypeDemo4() {<bean id="myBean"  scope="prototype">
  72.    
  73. </bean><bean id="myBean"  scope="prototype">
  74.    
  75. </bean>return "Prototype ID: " + prototypeBeanProvider.getObject().getId();<bean id="myBean"  scope="prototype">
  76.    
  77. </bean>}<bean id="myBean"  scope="prototype">
  78.    
  79. </bean>/**<bean id="myBean"  scope="prototype">
  80.    
  81. </bean> * 使用`@Lookup`注解获取原型Bean,每次返回不同id<bean id="myBean"  scope="prototype">
  82.    
  83. </bean> * @return<bean id="myBean"  scope="prototype">
  84.    
  85. </bean> */<bean id="myBean"  scope="prototype">
  86.    
  87. </bean>@GetMapping("/prototypeDemo5")<bean id="myBean"  scope="prototype">
  88.    
  89. </bean>public String prototypeDemo5() {<bean id="myBean"  scope="prototype">
  90.    
  91. </bean><bean id="myBean"  scope="prototype">
  92.    
  93. </bean>return "Prototype ID: " + getPrototypeBean().getId();<bean id="myBean"  scope="prototype">
  94.    
  95. </bean>}<bean id="myBean"  scope="prototype">
  96.    
  97. </bean>@Lookup<bean id="myBean"  scope="prototype">
  98.    
  99. </bean>public abstract PrototypeInjectBean getPrototypeBean();}
复制代码

  • 办理办法1: Spring 提供了ObjectProvider接口(继承自Provider接口),它允许延迟查找和实例化 Bean,非常适合在单例 Bean 中按需获取原型 Bean 的新实例。
通过访问接口/prototypeDemo4可以发现每次返回的id值是不同的。

  • 办理办法2: 可以通过定义一个工厂方法来创建原型 Bean 的实例,然后在单例 Bean 中注入这个工厂方法,每次必要时调用工厂方法获取新实例。
通过访问接口/prototypeDemo2可以发现每次返回的id值是不同的。

  • 办理办法3: 通过@Lookup注解,@Lookup注解是Spring框架中的一个特殊注解,用于在Spring容器中查找另一个Bean,并将其注入到当前Bean中。留意使用@Lookup注解的方法必须是抽象的(abstract)。
通过访问接口/prototypeDemo5可以发现每次返回的id值是不同的。
关于作者

来自一线全栈步伐员nine的探索与实践,连续迭代中。
接待关注或者点个小红心~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao1
2
3.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王國慶

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表