该示例只是一个虚拟示例,它显示了我遇到的问题,因此不要以其他方式陷入困境以解决此处的具体问题。我的问题更多是关于了解在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是 not 托管的(没有@Component
,没有@Value
),并且我有一个托管服务(控制器),该服务调用构造函数常规Info
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中有一种更简单的方法可以完成此操作。有吗?
答案 0 :(得分:1)
似乎在请求到来时需要一个托管的新对象。为此,您可以使用@Scope("prototype")
标记Bean,以解决您的问题。
具有原型范围的bean每次从容器请求时都会返回不同的实例。
要了解更多信息,请访问以下页面来了解原型的工作原理:
答案 1 :(得分:1)
这一切都取决于变化的事物。您可以使用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;
}
}
还要记住,将注入到Info
中的InfoRestController
实例是代理,因此在此示例中,您将返回带有一些其他字段的代理实例。为了克服这个问题,我们可以从注入的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
}
答案 2 :(得分:1)
这个难题的缺失部分是javax.inject.Provider
。我对此一无所知,但它恰好具有我要寻找的接口。最终的解决方案确实是让Spring管理bean(Info
)并在rest控制器中使用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();
}
}
出于完整性考虑,我发现了另一种更丑陋的方法,方法是注入弹簧ApplicationContext
,然后使用context.getBean("info")
,但是对spring上下文和字符串名称的依赖是一种气味。 Provider
的解决方案更具针对性