[英]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。 这是依赖注入的要点之一
我想到了一些潜在的解决方案:
InfoFactory
FactoryBean的引用,然后return factory.getObject();
. 但是我真的需要为这样一个简单的案例创建一个新的 class 吗?@Bean
工厂方法。 这仍然存在该方法显式实例化Info
POJO 的问题,因此它本身需要对其进行 Spring 注入。 此外,这是完整的样板文件。 Info
bean 的构造非常简单,我想在 Spring 中有一种更简单的方法可以实现这一点。 在那儿?
当请求到来时,您似乎需要一个托管的新 object。 为此,您可以使用@Scope("prototype")
标记您的 Bean,这将解决您的问题。
具有原型 scope 的 bean 将在每次从容器请求时返回不同的实例。
要了解更多,原型是如何工作的,请查看此页面:
这一切都取决于变化的事物是什么。 您可以使用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;
}
}
另请记住,注入InfoRestController
的Info
实例将被代理,因此在此示例中,您将返回一个带有一些附加字段的代理实例。 为了克服这个问题,我们可以从注入的 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.