【问题标题】:How to generate new managed bean programmatically in Spring如何在 Spring 中以编程方式生成新的托管 bean
【发布时间】:2019-10-30 16:03:11
【问题描述】:

这个例子只是一个虚拟的例子来展示我遇到的问题,所以不要太着迷于用其他方法来解决这里的具体问题。我的问题更多是关于理解在 Spring 中解决某类问题的正确技术

假设我有一个托管 bean Info

@Component
public class Info {
  private final String activeProfile;
  private final Instant timestamp;

  public Info(@Value("${spring.profiles.active}") String activeProfile) {
    this.activeProfile = activeProfile;
    this.timestamp = Instant.now();
  }
}

这里的关键是 bean 需要 Spring 注入的东西(我的示例中的活动配置文件)以及每次创建 bean 时都会更改的东西(我的示例中的时间戳)。由于后者,我不能使用Singleton 范围。获取此类 bean 的新实例的正确方法是什么?

我目前拥有的是,bean 不是托管的(没有@Component,没有@Value),我有一个托管服务(控制器)调用常规@的构造函数987654327@ POJO 明确。类似的东西

@RestController
public class InfoRestController { 
  @GetMapping
  public Info getInfo(@Value("${spring.profiles.active}") String activeProfile) {
    return new Info(activeProfile);
  } 
}

这个解决方案的问题在于,它会将活动配置文件的知识泄露给控制器,只是为了将其传递给Info 的构造函数,而从概念上讲,控制器不应该知道构造 Info bean。这就是依赖注入的要点之一

我想到了一些可能的解决方案:

  • 在控制器中引用InfoFactory FactoryBean,然后引用return factory.getObject();。但是我真的需要为这样一个简单的案例创建一个新类吗?
  • 有一个构造托管 bean 的 @Bean 工厂方法。这仍然存在该方法显式实例化Info POJO的问题,因此它本身需要对其进行Spring注入。此外,这是完整的样板文件。

Info bean 的构造非常简单,我想在 Spring 中有一种更简单的方法可以实现这一点。有吗?

【问题讨论】:

    标签: java spring spring-boot dependency-injection


    【解决方案1】:

    当请求到来时,您似乎需要一个托管的新对象。为此,您可以使用@Scope("prototype") 标记您的Bean,这将解决您的问题。

    具有原型作用域的 bean 每次从容器请求时都会返回不同的实例。

    要了解更多原型的工作原理,请查看此页面:

    1. Spring - Prototype scope example using @Scope annotation
    2. 3. Prototype Scope

    【讨论】:

    • 我会把那个注释放在哪里?在您的示例中,它始终作为工厂方法的一部分,我希望避免这种情况(我在列表中的第二个潜在选项)。或者你的意思是在 RestController getInfo 方法中?你能举例说明你的意思吗
    • 还有,谁来实例化 bean?即,控制器如何获取原型范围的 bean 以返回它?
    【解决方案2】:

    这完全取决于变化的东西。您可以使用 protoype 范围,如 ruhul 的回答所示。然而这里的问题是原型实例只会在InfoRestController bean 被创建时被注入一次。

    您可以尝试使用 request 范围 - 当您自动装配具有此类范围的 bean 时,会为每个 HTTP 请求创建一个新实例:

    @Component
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class Info {
        private final String activeProfile;
        private final Instant timestamp;
    
        public Info(@Value("${spring.profiles.active}") String activeProfile) {
            this.activeProfile = activeProfile;
            this.timestamp = Instant.now();
        }
    }
    

    这将确保为每个请求创建一个新的 Info 实例:

    @RestController
    public class InfoRestController {
    
        private Info info;
    
        @Autowired
        public InfoRestController(Info info) {
            this.info = info;
        }
    
        @GetMapping("/test")
        public Info get() {
            return info;
        }
    }
    

    还要记住,注入InfoRestControllerInfo 实例将被代理,因此在本例中,您将返回一个带有一些附加字段的代理实例。为了克服这个问题,我们可以从注入的 bean 中复制一个值:

    @RestController
    public class InfoRestController {
    
        private Info info;
    
        @Autowired
        public InfoRestController(Info info) {
            this.info = info;
        }
    
        @GetMapping("/test")
        public Info get() {
            return Info.of(info.getActiveProfile(), info.getTimestamp()); // create a copy
        }
    }
    
    @Component
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class Info {
    
        private final String activeProfile;
        private final Instant timestamp;
    
        @Autowired // this constructor should be used by spring
        public Info(@Value("${spring.profiles.active}") String activeProfile) {
            this.activeProfile = activeProfile;
            this.timestamp = Instant.now();
        }
    
        private Info(String activeProfile, Instant timestamp) { // private constructor
            this.activeProfile = activeProfile;
            this.timestamp = timestamp;
        }
    
        public static Info of(String activeProfile, Instant instant) { //static factory method
            return new Info(activeProfile, instant);
        }
    
        // getters
    }
    

    【讨论】:

    • 我沿着这条路走,但是 pojo 中的额外数据很烦人,而且很难删除。无论如何,这也有效。谢谢
    【解决方案3】:

    拼图中缺少的部分是javax.inject.Provider。我不知道它,但它具有我正在寻找的界面。最终的解决方案是确实让 Spring 管理 bean (Info) 并在其余控制器中使用 Provider。这是 bean,几乎和 OP 中的一模一样

    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public class Info {
      private final String activeProfile;
      private final Instant timestamp;
    
      public Info(@Value("${spring.profiles.active}") String activeProfile) {
        this.activeProfile = activeProfile;
        this.timestamp = Instant.now();
      }
    }
    

    该 bean 具有 ruhul 建议的 Prototype 范围。我之前尝试过,但没有Provider 我仍然卡住了。这是如何在控制器中返回它

    @RestController
    public class InfoRestController { 
      @Autowire
      private Provider<Info> infoProvider;
    
      @GetMapping
      public Info getInfo() {
        return infoProvider.get();
      } 
    }
    

    为了完整起见,我发现了另一种更丑陋的方法,即注入 spring ApplicationContext 然后使用 context.getBean("info") 但对 spring 上下文和字符串名称的依赖是一种气味。 Provider 的解决方案更加专注

    【讨论】:

    • 所以每次你调用Provider::get你都会从上下文中得到一个原型bean的新实例?这个真不错,不知道Provider
    • 是的。它与 spring 上下文集成。我在一些随机的 SO 评论中发现了它
    猜你喜欢
    • 2015-04-07
    • 2011-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-13
    • 2015-06-25
    • 2015-08-22
    • 1970-01-01
    相关资源
    最近更新 更多