请考虑以下情形 . 我有一个Spring应用程序上下文,其bean属性应该是可配置的,想想 DataSource
或 MailSender
. 可变应用程序配置由一个单独的bean管理,我们称之为 configuration
.
管理员现在可以更改配置值,如电子邮件地址或数据库URL,我想在运行时重新初始化配置的bean .
假设我不能简单地修改上面的可配置bean的属性(例如由 FactoryBean
或构造函数注入创建),但必须重新创建bean本身 .
有关如何实现这一点的任何想法?我很高兴收到有关如何组织整个配置的建议 . 没有什么是固定的 . :-)
EDIT
为了澄清一点:我不是问如何更新配置或如何注入静态配置值 . 我会尝试一个例子:
<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 回答
我可以想到一个'holder bean'方法(本质上是一个装饰器),持有者bean委托给holdee,它是持有者bean,它作为依赖注入其他bean . 没有其他人提到持有人而是持有人 . 现在,当更改holder bean的配置时,它会使用这个新配置重新创建一个holdee并开始委托给它 .
以下是我过去的工作方式:运行依赖于配置的服务,可以动态更改实现生命周期界面:IRefreshable:
控制器(或服务),可以修改配置广播到配置已更改的JMS主题的配置(提供配置对象的名称) . 然后,消息驱动的bean在实现IRefreshable的所有bean上调用IRefreshable接口 Contract .
spring的优点在于,您可以自动检测应用程序上下文中需要刷新的任何服务,从而无需显式配置它们:
这种方法在群集应用程序中运行得特别好,其中许多应用程序服务器中的一个可能会更改配置,然后需要注意这些配置 . 如果要使用JMX作为触发更改的机制,则JMX bean可以在更改任何属性时广播到JMS主题 .
你应该看看JMX . Spring也为此提供支持 .
Spring 2.0.x
Spring 2.5.x
Spring 3.0.x
Further updated answer to cover scripted bean
spring 2.5.x支持的另一种方法是脚本bean . 您可以为脚本使用各种语言 - BeanShell可能是最直观的,因为它具有与Java相同的语法,但它确实需要一些外部依赖项 . 但是,这些示例都在Groovy中 .
Spring Documentation的第24.3.1.2节介绍了如何配置它,但这里有一些显着的摘录,说明了我编辑的方法,使它们更适用于您的情况:
Groovy脚本看起来像这样:
由于系统管理员想要进行更改,因此他们(或您)可以适当地编辑脚本的内容 . 该脚本不是已部署应用程序的一部分,可以引用已知文件位置(或在启动期间通过标准PropertyPlaceholderConfigurer配置的文件位置) .
虽然该示例使用Groovy类,但您可以使用类执行代码来读取简单的属性文件 . 以这种方式,您永远不会直接编辑脚本,只需触摸它即可更改时间戳 . 然后该操作会触发重新加载,从而触发(更新的)属性文件中的属性刷新,最终更新Spring上下文中的值,然后关闭 .
文档确实指出这种技术不适用于构造函数注入,但也许你可以解决这个问题 .
Updated answer to cover dynamic property changes
Quoting from this article,其中provides full source code,一个方法是:
Original answer below covering static property changes:
听起来你只想将外部属性注入Spring上下文中 .
PropertyPlaceholderConfigurer
专为此目的而设计:然后使用Ant语法占位符引用外部属性(如果您希望从Spring 2.5.5开始,可以嵌套)
然后确保只有admin用户和运行应用程序的用户才能访问application.properties文件 .
示例application.properties:
密码=土豚
或者您可以使用this similar question中的方法,因此也可以使用my solution:
方法是让bean通过属性文件配置,解决方案是
当属性发生变化时,
刷新整个applicationContext(自动使用计划任务或手动使用JMX)
使用专用的属性提供程序对象来访问所有属性 . 此属性提供程序将继续检查属性文件以进行修改 . 对于无法进行基于原型的属性查找的bean,register a custom event您的属性提供程序将在找到更新的属性文件时触发 . 具有复杂生命周期的bean需要监听该事件并自行刷新 .
这不是我试过的东西,我试图提供指针 .
假设您的应用程序上下文是AbstractRefreshableApplicationContext的子类(示例XmlWebApplicationContext,ClassPathXmlApplicationContext) . AbstractRefreshableApplicationContext.getBeanFactory()将为您提供ConfigurableListableBeanFactory的实例 . 检查它是否是BeanDefinitionRegistry的实例 . 如果是这样,你可以调用'registerBeanDefinition'方法 . 这种方法将与Spring实现紧密结合,
检查AbstractRefreshableApplicationContext和DefaultListableBeanFactory的代码(这是你调用'AbstractRefreshableApplicationContext getBeanFactory()'时得到的实现)
您可以在ApplicationContext中创建名为"reconfigurable"的自定义作用域 . 它创建并缓存此范围内所有bean的实例 . 在配置更改时,它会清除缓存并在第一次访问时使用新配置重新创建Bean . 为此,您需要将可重配置bean的所有实例包装到AOP作用域代理中,并使用Spring-EL访问配置值:将名为
config
的映射放入ApplicationContext并访问配置,如#{ config['key'] }
.选项1 :
将
configurable
bean注入DataSource
或MailSender
. 始终从这些bean中获取配置bean中的可配置值 .在
configurable
bean内部运行一个线程,定期读取外部可配置属性(文件等..) . 这样,在管理员更改了属性之后,configurable
bean将自行刷新,因此DataSource
将自动获取更新的值 .您无需实际实现"thread" - 阅读:http://commons.apache.org/configuration/userguide/howto_filebased.html#Automatic_Reloading
选项2(糟糕,我认为,但可能不是 - 取决于用例):
prototype
范围为DataSource
/MailSender
类型的bean创建新bean . 在bean的init中,重新读取属性 .选项3:我认为,@ mR_fr0g关于使用JMX的建议可能不是一个坏主意 . 你能做的是:
将您的配置bean公开为MBean(读http://static.springsource.org/spring/docs/2.5.x/reference/jmx.html)
请求您的管理员更改MBean上的配置属性(或在bean中提供接口以从其源触发属性更新)
这个MBean(你需要编写的一段新代码),必须保留Beans的引用(你想要更改/注入已更改的属性的那些) . 这应该很简单(通过setter注入或运行时获取bean名称/类)
当MBean上的属性发生更改(或触发)时,它必须在相应的bean上调用相应的setter . 这样,您的遗留代码不会更改,您仍然可以管理运行时属性更改 .
HTH!
您可能希望查看Spring Inspector一个可插件组件,该组件在运行时提供对任何基于Spring的应用程序的编程访问 . 您可以使用Javascript在运行时更改配置或管理应用程序行为 .
Here是编写自己的PlaceholderConfigurer的好主意,它跟踪属性的使用情况,并在发生配置更改时更改它们 . 但这有两个缺点:
它不适用于构造函数注入属性值 .
如果重新配置的bean在处理某些内容时收到更改的配置,则可以获得竞争条件 .
我的解决方案是复制原始对象 . 拳头我创建了一个界面
为了简化函数的实现,请创建一个包含所有字段的构造函数,因此 IDE 可以帮助我一点 . 然后,您可以创建一个使用相同函数
Updateable#copyObject(T originalObject)
的复制构造函数 . 您还可以利用 IDE 创建的构造函数的代码来创建要实现的函数:我在Controller中使用它来更新应用程序的当前设置:
所以具有应用程序配置的
currentSettingsData
将具有更新的值,位于newSettingsData
. 这些方法允许更新任何bean而没有高复杂性 .