我可以在运行时替换Spring bean定义吗?


68

请考虑以下情形。我有一个带有bean的Spring应用程序上下文,其属性应该是可配置的,认为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>

因此,有一个constructorInjectedBean使用构造函数注入的bean 。想像一下,bean的构建非常昂贵,因此,使用原型范围或工厂代理不是一种选择DataSource

我想做的是,每次更新配置时(通过重新创建configurationServicebeanconstructorInjectedBean并将其重新注入到应用程序上下文和从属bean中)。

我们可以放心地假设constructorInjectedBean正在使用接口,因此代理魔术确实是一种选择。

我希望这个问题更加清楚。


因此,configuration需要在运行时更新Bean-还是每次管理员更改值时都需要更新Bean?我那个你的问题吗?还是要DataSource/ MailSenderbean在运行时使用更新的配置?还是两者?
madhurtanwani 2010年

这是第二个:我想在运行时更新注入的配置值(请参阅OP中的编辑)。
菲利普·贾达斯

Answers:


12

我可以想到一种“持有人bean”方法(本质上是一个装饰器),其中持有人bean委托给holdee,而持有人bean作为依赖项注入到其他bean中。除持有人外,没有其他人提及持有人。现在,当更改Holder bean的配置时,它将使用此新配置重新创建持有人,并开始委派给它。


我打开此页面只是为了找到此答案。
msangel 2013年

1
知道了什么,将近一年后我找到了这个答案,它反映了我最终如何解决该问题。装饰器实现所需对象的接口,将其注册为配置服务的观察者,并在必要时重新创建有问题的对象。
菲利普·贾达斯

@Philipp Jardas感谢您抽出宝贵时间回来,并提供有关您选择的实际实现的更新。非常感激!
shrini1000 2014年

29

这是我过去的操作方式:运行依赖于配置的服务,这些配置可以随时更改,以实现生命周期接口: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主题。


我还应该提到,使用JMX时,您必须做一些额外的工作才能获得JMX的安全性,以委托给应用程序的安全模型。使用上述方法,由于配置更改是使用Web GUI(假设您拥有一个)并重新使用现有应用程序安全模型进行的,因此问题不大。由于刷新只是通过JMS“建议”的,因此实际上并不需要对其进行保护(为此)。
贾斯汀2010年

这种方法的问题在于,对于应用程序正在处理的其他请求,刷新应该是原子操作。否则,请求将在其处理过程中以更改的配置进行处理。这可能会导致不可预测的行为。因此,从本质上讲,您将不得不以某种方式阻止使用应用程序上下文的请求。但是,您不仅可以刷新应用程序上下文本身。
SpaceTrucker

11

您应该看看JMX。Spring也为此提供支持。


谢谢你的建议。我必须承认,关于JMX,我是一个新手。您能否提供一些有关如何实现所需行为的提示?
菲利普·贾达斯

@Philipp:使用Spring的JMX非常简单。您制作了一些类似于Javabean的方法(即获取/设置对),使您可以操纵要调整的值,并使用注释类@ManagedResource和Bean方法@ManagedAttribute。然后,您将能够使用JMX客户端连接到正在运行的实例(我jvisualvm与适当的插件一起使用)。
Donal Fellows 2010年

@Donal:感谢您的回答。但是,我仍然看不到如何解决上述问题。JMX管理将如何帮助更改应用程序上下文中的类的实例?
菲利普·贾达斯

@Philipp:JMX可以到达并直接调用(带有适当注释的)对象上的方法。如果您看不到它对运行时重新配置有什么帮助,那么,话就逃不过我了……
Donal Fellows 2010年

@Donal:我可以清楚地看到JMX将如何帮助我重新配置属性配置的bean,以及其他一些答案中描述的方法。但是,我具体要求一种重新配置构造函数注入的或bean工厂创建的bean的方法,对于JMX而言,JMX似乎并不比所提及的任何其他方法更合适。我搞错了吗?
菲利普·贾达斯

2

进一步更新的答案涵盖了脚本豆

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>

然后,确保只有管理员用户和运行该应用程序的用户才能访问application.properties文件。

示例application.properties:

密码=土豚


感谢你的回答。我实际上是在询问如何在运行时更新属性(请参阅OP中的编辑)。
菲利普·贾达斯

@Philipp OK,在这种情况下,该讨论可能会有所帮助(它适用于Spring的旧版本,但可以进行更新)。阅读有关如何使其与Map条目等一起使用的注释的广泛注释:wuenschenswert.net/wunschdenken/archives/127
Gary Rowe,2010年

@Gary,感谢您的回复。我检查了您提到的博客,发现自己已经创建了类似的东西。但是,我仍然不知道如何不仅可以更新bean的属性,而且如何替换bean实例本身。为了便于讨论,我们假设我们正在谈论用DataSource创建的的属性FactoryBean。提到的方法只会更新工厂bean的值,这根本没有帮助。:-(
Philipp Jardas,2010年

@Philipp没问题。作为解决问题的最后尝试,您可以看一下bean脚本(有关详细信息,请参见答案中的我的编辑)
Gary Rowe 2010年

@Gary,非常感谢您提供详尽的答案和使用脚本的有趣方法。Buuuuuut,您知道这将会发生:“不适用于构造函数注入”-您自己说了。:-)现在您实际上已经提出了一种设置现有bean属性的理想方法。但是,如何管理用构造函数注入或工厂bean创建的实例?
菲利普·贾达斯

2

或者你可以使用从接近这个类似的问题,因此也是我的解决办法

方法是通过属性文件配置bean,解决方案是

  • 属性更改后,刷新整个applicationContext(自动使用计划任务或手动使用JMX)
  • 使用专用的属性提供程序对象访问所有属性。该属性提供程序将继续检查属性文件以进行修改。对于无法进行基于原型的属性查找的bean,请注册一个自定义事件,您的属性提供者将在找到更新的属性文件时将其触发。您的具有复杂生命周期的bean将需要侦听该事件并刷新自身。

1

这不是我尝试过的事情,我正在尝试提供指针。

假设您的应用程序上下文是AbstractRefreshableApplicationContext的子类(例如XmlWebApplicationContext,ClassPathXmlApplicationContext)。AbstractRefreshableApplicationContext.getBeanFactory()将为您提供ConfigurableListableBeanFactory的实例。检查它是否是BeanDefinitionRegistry的实例。如果是这样,您可以调用“ registerBeanDefinition”方法。这种方法将与Spring实施紧密结合,

检查AbstractRefreshableApplicationContext和DefaultListableBeanFactory的代码(这是调用“ AbstractRefreshableApplicationContext getBeanFactory()”时获得的实现)


从根本上来说,这可能是一个不错的主意。的接口registerBeanDefinition()org.springframework.beans.factory.support.BeanDefinitionRegistry。我会调查一下,谢谢。
菲利普·贾达斯

是的。也不要忘记在应用程序上下文中设置“ allowBeanDefinitionOverriding”。
Adisesha 2010年

1

您可以在ApplicationContext中创建一个称为“可重新配置”的自定义范围。它创建并缓存该范围内所有bean的实例。更改配置后,它将清除高速缓存,并在首次访问新配置时重新创建Bean。为此,您需要将所有可重配置Bean实例包装到AOP范围的代理中,并使用Spring-EL访问配置值:将一个称为map的映射config放入ApplicationContext并访问诸如的配置#{ config['key'] }


0

选项1 :

  1. configurableBean注入DataSource或中MailSender。始终从这些Bean中获取来自配置Bean的可配置值。
  2. configurableBean内运行一个线程,以定期读取外部可配置属性(文件等)。这样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(您将需要编写的一段新的Java代码),必须保留Bean(您要更改/将更改的属性注入/注入到其中)的引用。这应该很简单(通过setter注入或运行时获取bean名称/类)
    1. 当MBean的属性被更改(或触发)时,它必须在各个bean上调用适当的setter。这样,您的旧代码不会更改,您仍然可以管理运行时属性更改。

HTH!



如果我无法控制已配置的类,则选项1可能无法工作。而且,如果我控制了它们,它们将被配置代码“污染”,这对我来说听起来不好。在我的情况下,选项2不是一个选择,因为bean的生命周期很昂贵。您不想将原型作用域应用于数据源...请参阅OP,以获取具有说明性的修改。
菲利普·贾达斯

<ahref=" stackoverflow.com/questions/2008175/…问题</a>似乎并没有解决我的问题……
Philipp Jardas,2010年

我不确定“污染”是否正确-您的类需要外部定义的配置。现在,您要么必须将属性(随着属性的改变)“注入”到这些类中(在这种情况下,我认为注入道具的类必须知道需要配置的此类),否则这些类必须在发生更改时将更改拉入通过中央配置管理器
madhurtanwani 2010年

我考虑过污染方面,并认为您是对的,如果不将配置方面混入类中,可能就无法解决此问题。但是,我仍然不知道如何更新使用构造函数注入或工厂bean创建的bean。:-(
Philipp Jardas,2010年

0

您可能想看看Spring Inspector,它是一个可插入插件的组件,可在运行时提供对任何基于Spring的应用程序的编程访问。您可以使用Javascript在运行时更改配置或管理应用程序的行为。


0

是编写自己的PlaceholderConfigurer的好主意,该PlaceholderConfigurer可以跟踪属性的使用并在配置发生更改时对其进行更改。但是,这有两个缺点:

  1. 它不适用于构造函数注入属性值。
  2. 如果重新配置的bean在处理某些东西时收到更改的配置,则可以得到竞争条件。

0

我的解决方案是复制原始对象。拳头我创建了一个界面

/**
 * 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,而没有很高的复杂性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.