繁体   English   中英

如何在 Spring 中以编程方式生成新的托管 bean

[英]How to generate new managed bean programmatically in Spring

这个例子只是一个虚拟的例子来展示我遇到的问题,所以不要太着迷于用其他方法来解决这里的具体问题。 我的问题更多是关于了解解决 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 scope。 获取此类 bean 的新实例的正确方法是什么?

我目前拥有的是,bean不是托管的(没有@Component ,没有@Value ),我有一个托管服务(一个控制器),它显式地调用常规Info POJO 的构造函数。 就像是

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

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

我想到了一些潜在的解决方案:

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

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

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

具有原型 scope 的 bean 将在每次从容器请求时返回不同的实例。

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

  1. Spring - 原型 scope 示例使用 @Scope 注释
  2. 3.原型Scope

这一切都取决于变化的事物是什么。 您可以使用protoype scope,如 ruhul 的回答所示。 然而,这里的问题是原型实例只会在创建InfoRestController bean 时注入一次。

您可以尝试使用request scope - 当您使用此类 scope 自动装配 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
}

缺少的部分是javax.inject.Provider 我不知道它,但它具有我正在寻找的界面。 最终的解决方案是确实让 Spring 管理 bean ( Info ) 并在 rest controller 中使用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();
  }
}

如 ruhul 所建议的,该 bean 具有原型 scope。 我之前尝试过,但没有Provider我仍然卡住了。 这是如何在 controller 中返回它

@RestController
public class InfoRestController { 
  @Autowire
  private Provider<Info> infoProvider;

  @GetMapping
  public Info getInfo() {
    return infoProvider.get();
  } 
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM