簡體   English   中英

我可以在運行時替換Spring bean定義嗎?

[英]Can I replace a Spring bean definition at runtime?

請考慮以下情形。 我有一個Spring應用程序上下文,其bean屬性應該是可配置的, MailSender DataSourceMailSender 可變應用程序配置由一個單獨的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:

密碼=土豚

或者你可以使用這個類似問題的方法,因此也是我的解決方案

方法是讓bean通過屬性文件配置,解決方案是

  • 當屬性發生變化時,刷新整個applicationContext(自動使用計划任務或手動使用JMX)
  • 使用專用的屬性提供程序對象來訪問所有屬性。 此屬性提供程序將繼續檢查屬性文件以進行修改。 對於無法進行基於原型的屬性查找的bean,請注冊屬性提供程序在找到更新的屬性文件時將觸發的自定義事件 具有復雜生命周期的bean需要監聽該事件並自行刷新。

您可以在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的好主意,它跟蹤屬性的使用並在發生配置更改時更改它們。 但這有兩個缺點:

  1. 它不適用於構造函數注入屬性值。
  2. 如果重新配置的bean在處理某些內容時收到更改的配置,則可以獲得競爭條件。

我的解決方案是復制原始對象。 拳頭我創建了一個界面

/**
 * 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 :

  1. 將可configurable bean注入DataSourceMailSender 始終從這些bean中獲取配置bean中的可配置值。
  2. 在可configurable bean內部運行一個線程來定期讀取外部可配置屬性(文件等..)。 這樣,可configurable bean將在管理員更改屬性后自行刷新,因此DataSource將自動獲取更新的值。

選項2(糟糕,我認為,但可能不是 - 取決於用例):

  1. 始終為DataSource / MailSender類型的bean創建新bean - 使用prototype范圍。 在bean的init中,重新讀取屬性。

選項3:我認為,@ mR_fr0g關於使用JMX的建議可能不是一個壞主意。 你能做的是:

  1. 將配置bean公開為MBean(請參閱http://static.springsource.org/spring/docs/2.5.x/reference/jmx.html
  2. 請求您的管理員更改MBean上的配置屬性(或在bean中提供接口以從其源觸發屬性更新)
  3. 這個MBean(你需要編寫的一段新代碼)必須保留Beans(你想要更改/注入已更改屬性的那些)的引用。 這應該很簡單(通過setter注入或bean名稱/類的運行時獲取)
    1. 當MBean上的屬性被更改(或觸發)時,它必須在相應的bean上調用相應的setter。 這樣,您的遺留代碼不會更改,您仍然可以管理運行時屬性更改。

HTH!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM