![](/img/trans.png)
[英]How can I generate/create new spring beans at runtime with a bean definition object?
[英]Can I replace a Spring bean definition at runtime?
請考慮以下情形。 我有一個Spring應用程序上下文,其bean屬性應該是可配置的, MailSender
DataSource
或MailSender
。 可變應用程序配置由一個單獨的bean管理,讓我們稱之為configuration
。
管理員現在可以更改配置值,如電子郵件地址或數據庫URL,我想在運行時重新初始化配置的bean。
假設我不能簡單地修改上面的可配置bean的屬性(例如,由FactoryBean
或構造函數注入創建),但必須重新創建bean本身。
有關如何實現這一點的任何想法? 我很高興收到有關如何組織整個配置的建議。 沒有什么是固定的。 :-)
編輯
為了澄清一點:我不是問如何更新配置或如何注入靜態配置值。 我會嘗試一個例子:
<beans>
<util:map id="configuration">
<!-- initial configuration -->
</util:map>
<bean id="constructorInjectedBean" class="Foo">
<constructor-arg value="#{configuration['foobar']}" />
</bean>
<bean id="configurationService" class="ConfigurationService">
<property name="configuration" ref="configuration" />
</bean>
</beans>
所以有一個使用構造函數注入的bean constructorInjectedBean
。 想象一下,bean的構造非常昂貴,因此使用原型范圍或工廠代理不是一種選擇,想想DataSource
。
我想要做的是每次更新configurationService
(通過configurationService
,bean constructorInjectedBean
被重新創建並重新注入應用程序上下文和依賴bean。
我們可以放心地假設constructorInjectedBean
正在使用接口,因此代理魔術確實是一個選項。
我希望能讓這個問題更清楚一些。
以下是我過去的工作方式:運行依賴於配置的服務,可以動態更改實現生命周期界面:IRefreshable:
public interface IRefreshable {
// Refresh the service having it apply its new values.
public void refresh(String filter);
// The service must decide if it wants a cache refresh based on the refresh message filter.
public boolean requiresRefresh(String filter);
}
控制器(或服務),可以修改配置廣播到配置已更改的JMS主題的配置(提供配置對象的名稱)。 然后,消息驅動的bean在實現IRefreshable的所有bean上調用IRefreshable接口契約。
spring的優點在於,您可以自動檢測應用程序上下文中需要刷新的任何服務,從而無需顯式配置它們:
public class MyCacheSynchService implements InitializingBean, ApplicationContextAware {
public void afterPropertiesSet() throws Exception {
Map<String, ?> refreshableServices = m_appCtx.getBeansOfType(IRefreshable.class);
for (Map.Entry<String, ?> entry : refreshableServices.entrySet() ) {
Object beanRef = entry.getValue();
if (beanRef instanceof IRefreshable) {
m_refreshableServices.add((IRefreshable)beanRef);
}
}
}
}
這種方法在群集應用程序中尤其有效,其中許多應用程序服務器中的一個可能會更改配置,然后需要注意這些配置。 如果要使用JMX作為觸發更改的機制,則JMX bean可以在更改其任何屬性時廣播到JMS主題。
我可以想到一個'holder bean'方法(本質上是一個裝飾器),持有者bean委托給holdee,它是持有者bean,它作為依賴注入其他bean。 沒有其他人提到持有人而是持有人。 現在,當更改holder bean的配置時,它會使用這個新配置重新創建一個holdee並開始委托給它。
你應該看看JMX 。 Spring也為此提供支持。
進一步更新的答案涵蓋腳本bean
spring 2.5.x +支持的另一種方法是腳本bean。 您可以為腳本使用各種語言 - BeanShell可能是最直觀的,因為它具有與Java相同的語法,但它確實需要一些外部依賴項。 但是,這些示例都在Groovy中。
Spring文檔的第24.3.1.2節介紹了如何配置它,但這里有一些顯着的摘錄,說明了我編輯的方法,使它們更適用於您的情況:
<beans>
<!-- This bean is now 'refreshable' due to the presence of the 'refresh-check-delay' attribute -->
<lang:groovy id="messenger"
refresh-check-delay="5000" <!-- switches refreshing on with 5 seconds between checks -->
script-source="classpath:Messenger.groovy">
<lang:property name="message" value="defaultMessage" />
</lang:groovy>
<bean id="service" class="org.example.DefaultService">
<property name="messenger" ref="messenger" />
</bean>
</beans>
Groovy腳本看起來像這樣:
package org.example
class GroovyMessenger implements Messenger {
private String message = "anotherProperty";
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message
}
}
由於系統管理員想要進行更改,因此他們(或您)可以適當地編輯腳本的內容。 該腳本不是已部署應用程序的一部分,可以引用已知文件位置(或在啟動期間通過標准PropertyPlaceholderConfigurer配置的文件位置)。
雖然該示例使用Groovy類,但您可以使用類執行代碼來讀取簡單的屬性文件。 以這種方式,您永遠不會直接編輯腳本,只需觸摸它即可更改時間戳。 然后該操作會觸發重新加載,從而觸發(更新的)屬性文件中的屬性刷新,最終更新Spring上下文中的值,然后關閉。
文檔確實指出這種技術不適用於構造函數注入,但也許你可以解決這個問題。
更新了答案以涵蓋動態屬性更改
* a factory bean that detects file system changes * an observer pattern for Properties, so that file system changes can be propagated * a property placeholder configurer that remembers where which placeholders were used, and updates singleton beans' properties * a timer that triggers the regular check for changed files
觀察者模式由接口和類ReloadableProperties,ReloadablePropertiesListener,PropertiesReloadedEvent和ReloadablePropertiesBase實現。 它們都不是特別令人興奮,只是正常的聽眾處理。 DelegatingProperties類用於在更新屬性時透明地交換當前屬性。 我們只會立即更新整個屬性映射,以便應用程序可以避免不一致的中間狀態(稍后會詳細介紹)。
現在可以編寫ReloadablePropertiesFactoryBean來創建一個ReloadableProperties實例(而不是像PropertiesFactoryBean那樣的Properties實例)。 當提示執行此操作時,RPFB會檢查文件修改時間,並在必要時更新其ReloadableProperties。 這會觸發觀察者模式機制。
在我們的例子中,唯一的監聽器是ReloadingPropertyPlaceholderConfigurer。 它的行為就像一個標准的Spring PropertyPlaceholderConfigurer,除了它跟蹤占位符的所有用法。 現在,當重新加載屬性時,將找到每個已修改屬性的所有用法,並再次分配這些單例bean的屬性。
以下原始答案涵蓋靜態屬性更改:
聽起來你只想將外部屬性注入Spring上下文中。 PropertyPlaceholderConfigurer
專為此目的而設計:
<!-- Property configuration (if required) -->
<bean id="serverProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<!-- Identical properties in later files overwrite earlier ones in this list -->
<value>file:/some/admin/location/application.properties</value>
</list>
</property>
</bean>
然后使用Ant語法占位符引用外部屬性(如果您希望從Spring 2.5.5開始,可以嵌套)
<bean id="example" class="org.example.DataSource">
<property name="password" value="${password}"/>
</bean>
然后確保只有admin用戶和運行應用程序的用戶才能訪問application.properties文件。
示例application.properties:
密碼=土豚
您可以在ApplicationContext中創建名為“reconfigurable”的自定義作用域。 它創建並緩存此范圍內所有bean的實例。 在配置更改時,它會清除緩存並在第一次訪問時使用新配置重新創建Bean。 為此,您需要將可重新配置bean的所有實例包裝到AOP作用域代理中,並使用Spring-EL訪問配置值:將名為config
的映射放入ApplicationContext並訪問配置,如#{ config['key'] }
。
這不是我試過的東西,我試圖提供指針。
假設您的應用程序上下文是AbstractRefreshableApplicationContext的子類(示例XmlWebApplicationContext,ClassPathXmlApplicationContext)。 AbstractRefreshableApplicationContext.getBeanFactory()將為您提供ConfigurableListableBeanFactory的實例。 檢查它是否是BeanDefinitionRegistry的實例。 如果是這樣,你可以調用'registerBeanDefinition'方法。 這種方法將與Spring實現緊密結合,
檢查AbstractRefreshableApplicationContext和DefaultListableBeanFactory的代碼(這是你調用'AbstractRefreshableApplicationContext getBeanFactory()'時得到的實現)
您可能希望查看Spring Inspector是一個可插件組件,它在運行時提供對任何基於Spring的應用程序的編程訪問。 您可以使用Javascript在運行時更改配置或管理應用程序行為。
這是編寫自己的PlaceholderConfigurer的好主意,它跟蹤屬性的使用並在發生配置更改時更改它們。 但這有兩個缺點:
我的解決方案是復制原始對象。 拳頭我創建了一個界面
/**
* Allows updating data to some object.
* Its an alternative to {@link Cloneable} when you cannot
* replace the original pointer. Ex.: Beans
* @param <T> Type of Object
*/
public interface Updateable<T>
{
/**
* Import data from another object
* @param originalObject Object with the original data
*/
public void copyObject(T originalObject);
}
為了簡化函數的實現,請創建一個包含所有字段的構造函數,這樣IDE可以幫助我一點。 然后,您可以創建一個使用相同函數Updateable#copyObject(T originalObject)
的復制構造函數。 您還可以利用IDE創建的構造函數的代碼來創建要實現的函數:
public class SettingsDTO implements Cloneable, Updateable<SettingsDTO>
{
private static final Logger LOG = LoggerFactory.getLogger(SettingsDTO.class);
@Size(min = 3, max = 30)
private String id;
@Size(min = 3, max = 30)
@NotNull
private String name;
@Size(min = 3, max = 100)
@NotNull
private String description;
@Max(100)
@Min(5)
@NotNull
private Integer pageSize;
@NotNull
private String dateFormat;
public SettingsDTO()
{
}
public SettingsDTO(String id, String name, String description, Integer pageSize, String dateFormat)
{
this.id = id;
this.name = name;
this.description = description;
this.pageSize = pageSize;
this.dateFormat = dateFormat;
}
public SettingsDTO(SettingsDTO original)
{
copyObject(original);
}
@Override
public void copyObject(SettingsDTO originalObject)
{
this.id = originalObject.id;
this.name = originalObject.name;
this.description = originalObject.description;
this.pageSize = originalObject.pageSize;
this.dateFormat = originalObject.dateFormat;
}
}
我在Controller中使用它來更新應用程序的當前設置:
if (bindingResult.hasErrors())
{
model.addAttribute("settingsData", newSettingsData);
model.addAttribute(Templates.MSG_ERROR, "The entered data has errors");
}
else
{
synchronized (settingsData)
{
currentSettingData.copyObject(newSettingsData);
redirectAttributes.addFlashAttribute(Templates.MSG_SUCCESS, "The system configuration has been updated successfully");
return String.format("redirect:/%s", getDao().getPath());
}
}
因此,具有應用程序配置的currentSettingsData
將具有更新的值,位於newSettingsData
。 這些方法允許更新任何bean而沒有高復雜性。
選項1 :
configurable
bean注入DataSource
或MailSender
。 始終從這些bean中獲取配置bean中的可配置值。 configurable
bean內部運行一個線程來定期讀取外部可配置屬性(文件等..)。 這樣,可configurable
bean將在管理員更改屬性后自行刷新,因此DataSource
將自動獲取更新的值。
選項2(糟糕,我認為,但可能不是 - 取決於用例):
DataSource
/ MailSender
類型的bean創建新bean - 使用prototype
范圍。 在bean的init中,重新讀取屬性。 選項3:我認為,@ mR_fr0g關於使用JMX的建議可能不是一個壞主意。 你能做的是:
HTH!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.