我可以在运行时replaceSpring bean定义吗?
考虑以下情况。 我有一个Spring应用程序上下文,其属性应该是可configuration的,请考虑DataSource
或MailSender
。 可变的应用程序configuration由一个单独的beanpipe理,我们称之为configuration
。
pipe理员现在可以更改configuration值,如电子邮件地址或数据库URL,我想在运行时重新初始化configuration的bean。
假设我不能简单地修改上面的可configurationbean的属性(例如由FactoryBean
或构造函数注入创build),但必须重新创buildbean本身。
任何想法如何实现这一目标? 我很乐意收到关于如何组织整个configuration的build议。 没有什么是固定的 🙂
编辑
澄清一些事情:我不问如何更新configuration或如何注入静态configuration值。 我会尝试一个例子:
<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的构造是非常昂贵的,所以使用原型范围或工厂代理不是一种select,请考虑DataSource
。
我想要做的是每次configuration更新(通过configurationService
的bean constructorInjectedBean
正在重新创build和重新注入应用程序的上下文和依赖豆。
我们可以安全地假设constructorInjectedBean
正在使用一个接口,所以代理魔术确实是一个选项。
我希望能让问题更清楚些。
我可以想到一个持有者bean的方法(本质上是一个装饰器),持有者bean委托给holdee,持有者bean被注入为其他bean的依赖项。 其他人都没有提到holdee,而是持有人。 现在,当holder bean的configuration发生变化时,它会用这个新configuration重新创buildholdee,并开始委托给它。
以下是我过去的做法:运行依赖于可以在运行中更改的configuration的服务实现生命周期界面: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); }
控制器(或服务),可以将configuration的一部分广播修改为configuration已更改的JMS主题(提供configuration对象的名称)。 一个消息驱动bean然后调用所有实现IRefreshable的bean的IRefreshable接口契约。
Spring的好处在于,您可以自动检测应用程序上下文中需要刷新的任何服务,而不需要显式configuration它们:
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); } } } }
这种方法在集群应用程序中特别适用,其中许多应用程序服务器中的一个可能会更改configuration,这些都需要注意。 如果要使用JMX作为触发更改的机制,那么当其任何属性发生更改时,JMX bean就可以广播到JMS主题。
你应该看看JMX 。 Spring也为此提供支持。
- Spring 2.0.x
- Spring 2.5.x
- Spring 3.0.x
进一步更新的答案覆盖脚本的豆
Spring 2.5.x +支持的另一种方法是脚本bean。 你可以为你的脚本使用各种语言–BeanShell可能是最直观的,因为它和Java有相同的语法,但是它确实需要一些外部依赖。 但是,这些示例在Groovy中。
春季文档第24.3.1.2节介绍了如何configuration这个,但是这里有一些突出的摘录来说明我编辑的方法,使它们更适合于你的情况:
<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 } }
由于系统pipe理员想要进行更改,他们(或您)可以适当地编辑脚本的内容。 该脚本不是部署的应用程序的一部分,可以引用已知的文件位置(或在启动期间通过标准PropertyPlaceholderConfigurerconfiguration的位置)。
尽pipe这个例子使用了一个Groovy类,但是你可以让这个类执行读取一个简单属性文件的代码。 以这种方式,您不要直接编辑脚本,只需触摸它即可更改时间戳。 然后,该动作触发重新加载,这反过来会触发(更新)属性文件的属性刷新,最终更新Spring上下文中的值,然后离开。
文档指出,这种技术不适用于构造器注入,但也许你可以解决这个问题。
更新了涵盖dynamic属性更改的答案
从这篇 提供完整源代码的 文章中引用一种方法是:
* 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来创buildReloadableProperties实例(而不是像PropertiesFactoryBean那样的Properties实例)。 当提示时,RPFB将检查文件修改时间,并在必要时更新其ReloadableProperties。 这触发观察者模式机器。
在我们的例子中,唯一的监听器是ReloadingPropertyPlaceholderConfigurer。 它的行为就像一个标准的Spring PropertyPlaceholderConfigurer,只是它跟踪占位符的所有用法。 现在,当属性重新加载时,每个修改后的属性的所有用法都被find,并且这些单例bean的属性被重新赋值。
下面覆盖静态属性更改的原始答案:
听起来就像你只是想注入外部属性到你的Spring上下文中。 PropertyPlaceholderConfigurer
是为此目的而devise的:
<!-- 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文件只能由admin用户和运行该应用程序的用户访问。
示例application.properties:
密码=土豚
或者你可以使用这个类似的问题 ,因此也是我的解决scheme :
这个方法是通过属性文件来configurationbean,解决scheme也是这样
- 刷新整个应用程序上下文(自动使用计划任务或手动使用JMX)当属性已经改变或
- 使用专用的属性提供者对象来访问所有属性。 此属性提供程序将继续检查属性文件以进行修改。 对于不可能使用基于原型的属性查找的bean, 注册属性提供程序在find更新的属性文件时将触发的自定义事件 。 复杂生命周期的豆类将需要倾听这个事件并更新自己。
这不是我尝试过的,我正在努力提供指点。
假设您的应用程序上下文是AbstractRefreshableApplicationContext的一个子类(例如XmlWebApplicationContext,ClassPathXmlApplicationContext)。 AbstractRefreshableApplicationContext.getBeanFactory()将为您提供ConfigurableListableBeanFactory的实例。 检查它是否是BeanDefinitionRegistry的实例。 如果是这样,你可以调用“registerBeanDefinition”方法。 这种方法将与Spring实现紧密结合,
检查AbstractRefreshableApplicationContext和DefaultListableBeanFactory的代码(这是您在调用“AbstractRefreshableApplicationContext getBeanFactory()”时获得的实现)
您可以在ApplicationContext中创build一个名为“reconfigurable”的自定义作用域。 它在这个范围内创build和caching所有bean的实例。 在configuration更改时,将清除caching并使用新configuration首次访问时重新创buildbean。 为此,需要将所有可重新configurationbean的实例包装到AOP作用域代理中,并使用Spring-EL访问configuration值:将一个名为config
的映射放入ApplicationContext并访问configuration,如#{ config['key'] }
。
选项1 :
- 将可
configurable
bean注入到DataSource
或MailSender
。 始终从这些bean中获取configurationbean的可configuration值。 - 在可
configurable
bean内部运行一个线程,定期读取外部可configuration的属性(文件等)。 通过这种方式,可configurable
bean将在pipe理员更改属性后自动刷新,因此DataSource
将自动获取更新的值。- 您不必实际执行“线程” – 请阅读: http : //commons.apache.org/configuration/userguide/howto_filebased.html#Automatic_Reloading
选项2(不好,我想,但也许不是 – 取决于用例):
- 总是为
DataSource
/MailSender
types的bean创build新的bean – 使用prototype
范围。 在bean的init中,重新读取属性。
选项3:我认为,使用JMX的@ mR_fr0gbuild议可能不是一个坏主意。 你可以做的是:
- 将您的configurationbean公开为MBean(请阅读http://static.springsource.org/spring/docs/2.5.x/reference/jmx.html )
- 请求pipe理员更改MBean上的configuration属性(或者在bean中提供一个接口,以从源头触发属性更新)
- 这个MBean(你需要编写一段新的Java代码)必须保持Beans的引用(那些你想改变/注入已经改变的属性的)。 这应该是简单的(通过setter注入或运行时获取的bean名称/类)
- 当MBean上的属性被改变(或触发)时,它必须调用相应的bean上相应的setter。 这样,您的遗留代码不会改变,您仍然可以pipe理运行时属性更改。
HTH!
你可能想看一看Spring Inspector的一个插件组件,它可以在运行时提供对任何基于Spring的应用程序的编程访问。 您可以使用Javascript更改configuration或在运行时pipe理应用程序行为。
编写自己的PlaceholderConfigurer可以跟踪属性的使用情况,并在发生configuration更改时进行更改。 但是这有两个缺点:
- 它不适用于构造函数注入属性值。
- 如果重新configuration的bean在处理某些东西时收到更改的configuration,则可以获得竞争条件。
我的解决scheme是复制原始对象。 拳头我创造了一个界面
/** * 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); }
为了简化函数fist的实现,创build一个包含所有字段的构造函数,所以IDE可以帮助我一点。 然后你可以制作一个拷贝构造函数,使用相同的函数Updateable#copyObject(T originalObject)
。 您也可以利用由IDE创build的构造函数的代码来创build要实现的function:
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; } }
我用它在一个控制器来更新应用程序的当前设置:
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()); } }
因此,具有应用程序configuration的currentSettingsData
将具有更新的值,位于newSettingsData
。 这些方法允许更新任何bean而不需要太复杂。