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

时间:2010-10-28 09:02:39

标签: java spring

请考虑以下情形。我有一个Spring应用程序上下文,其bean属性应该是可配置的,请考虑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重建bean constructorInjectedBean并重新注入应用程序上下文和依赖bean。

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

我希望能让这个问题更加清晰。

11 个答案:

答案 0 :(得分: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主题。

答案 1 :(得分:11)

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

答案 2 :(得分:10)

我可以想到一个'holder bean'方法(本质上是一个装饰器),持有者bean委托给holdee,它是持有者bean,它作为依赖注入其他bean。没有其他人提到持有人而是持有人。现在,当更改holder bean的配置时,它会使用这个新配置重新创建一个holdee并开始委托给它。

答案 3 :(得分:2)

进一步更新回答以涵盖脚本bean

spring 2.5.x +支持的另一种方法是脚本bean。您可以为脚本使用各种语言 - BeanShell可能是最直观的,因为它具有与Java相同的语法,但它确实需要一些外部依赖项。但是,这些示例都在Groovy中。

Spring Documentation的第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上下文中的值,然后关闭。

文档确实指出这种技术不适用于构造函数注入,但也许你可以解决这个问题。

更新了涵盖动态属性更改的答案

Quoting from this articleprovides full source code,其中一种方法是:

* 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确实)。什么时候   提示这样做,RPFB检查   文件修改时间,如果   必要的,更新它   ReloadableProperties。这触发了   观察者模式机制。

     

在我们的案例中,唯一的听众是   ReloadingPropertyPlaceholderConfigurer。   它的行为就像一个标准的弹簧   PropertyPlaceholderConfigurer,除了   它跟踪的所有用法   占位符。现在当属性   重新加载,每个修改的所有用法   找到属性和属性   那些单身豆的分配   试。

以下原始答案涵盖静态属性更改:

听起来你只想将外部属性注入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:

密码=土豚

答案 4 :(得分:2)

或者您可以使用this similar question的方法,因此my solution

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

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

答案 5 :(得分:1)

这不是我尝试过的,我试图提供指示。

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

检查AbstractRefreshableApplicationContext和DefaultListableBeanFactory的代码(这是你调用'AbstractRefreshableApplicationContext getBeanFactory()'时得到的实现)

答案 6 :(得分:1)

您可以在ApplicationContext中创建一个名为“reconfigurable”的自定义作用域。它创建并缓存此范围内所有bean的实例。在配置更改时,它会清除缓存并在第一次访问时使用新配置重新创建Bean。为此,您需要将可重新配置Bean的所有实例包装到AOP作用域代理中,并使用Spring-EL访问配置值:将名为config的映射放入ApplicationContext并访问配置,如{{1} }。

答案 7 :(得分:0)

选项1:

  1. configurable bean注入DataSourceMailSender。始终从这些bean中获取配置bean中的可配置值。
  2. configurable bean内部运行一个线程,定期读取外部可配置属性(文件等..)。这样,configurable bean将在管理员更改属性后自行刷新,因此DataSource将自动获取更新的值。

  3. 选项2(糟糕,我认为,但可能不是 - 取决于用例):

    1. 始终使用DataSource范围为MailSender / prototype类型的bean创建新的bean。在bean的init中,重新读取属性。

    2. 选项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。这样,您的遗留代码不会更改,您仍然可以管理运行时属性更改。
      4. HTH!

答案 8 :(得分:0)

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

答案 9 :(得分:0)

Here是编写自己的PlaceholderConfigurer的好主意,它跟踪属性的使用情况,并在发生配置更改时更改它们。但这有两个缺点:

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

答案 10 :(得分: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。

相关问题