[英]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.