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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4041300/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 10:51:34  来源:igfitidea点击:

Can I replace a Spring bean definition at runtime?

javaspring

提问by Philipp Jardas

Consider the following scenario. I have a Spring application context with a bean whose properties should be configurable, think DataSourceor MailSender. The mutable application configuration is managed by a separate bean, let's call it configuration.

考虑以下场景。我有一个带有 bean 的 Spring 应用程序上下文,其属性应该是可配置的,thinkDataSourceMailSender. 可变应用程序配置由一个单独的 bean 管理,我们称之为configuration

An administrator can now change the configuration values, like email address or database URL, and I would like to re-initialize the configured bean at runtime.

管理员现在可以更改配置值,如电子邮件地址或数据库 URL,我想在运行时重新初始化配置的 bean。

Assume that I can't just simply modify the property of the configurable bean above (e.g. created by FactoryBeanor constructor injection) but have to recreate the bean itself.

假设我不能只是简单地修改上面的可配置 bean 的属性(例如通过FactoryBean或构造函数注入创建),而是必须重新创建 bean 本身。

Any thoughts on how to achieve this? I'd be glad to receive advice on how to organize the whole configuration thing as well. Nothing is fixed. :-)

关于如何实现这一目标的任何想法?我也很高兴收到有关如何组织整个配置的建议。没有什么是固定的。:-)

EDIT

编辑

To clarify things a bit: I am not asking how to update the configuration or how to inject static configuration values. I'll try an example:

澄清一下:我不是在问如何更新配置或如何注入静态配置值。我会尝试一个例子:

<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>

So there's a bean constructorInjectedBeanthat uses constructor injection. Imagine the construction of the bean is very expensive so using a prototype scope or a factory proxy is not an option, think DataSource.

所以有一个constructorInjectedBean使用构造函数注入的bean 。想象一下,bean 的构造非常昂贵,所以使用原型作用域或工厂代理不是一种选择,想想看DataSource

What I want to do is that every time the configuration is being updated (via configurationServicethe bean constructorInjectedBeanis being recreated and re-injected into the application context and dependent beans.

我想要做的是每次更新配置时(通过重新创建configurationServicebeanconstructorInjectedBean并重新注入应用程序上下文和依赖 bean。

We can safely assume that constructorInjectedBeanis using an interface so proxy magic is indeed an option.

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

I hope to have made the question a little bit clearer.

我希望让这个问题更清楚一点。

采纳答案by shrini1000

I can think of a 'holder bean' approach (essentially a decorator), where the holder bean delegates to holdee, and it's the holder bean which is injected as a dependency into other beans. Nobody else has a reference to holdee but the holder. Now, when the holder bean's config is changed, it recreates the holdee with this new config and starts delegating to it.

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

回答by madhurtanwani

Option 1 :

选项1 :

  1. Inject the configurablebean into the DataSourceor MailSender. Always get the configurable values from the configuration bean from within these beans.
  2. Inside the configurablebean run a thread to read the externally configurable properties (file etc..) periodically. This way the configurablebean will refresh itself after the admin had changed the properties and so the DataSourcewill get the updated values automatically.
  1. configurablebean 注入到DataSourceor 中MailSender。始终从这些 bean 中获取来自配置 bean 的可配置值。
  2. configurablebean内部运行一个线程来定期读取外部可配置的属性(文件等)。这样configurablebean 将在管理员更改属性后自行刷新,因此DataSource将自动获取更新的值。


Option 2 (bad, i think, but maybe not - depends on use case) :

选项 2(不好,我认为,但也许不是 - 取决于用例):

  1. Always create new beans for beans of type DataSource/ MailSender- using prototypescope. In the init of the bean, read the properties afresh.
  1. 始终为类型为DataSource/ 的bean 创建新 bean MailSender- 使用prototype范围。在 bean 的 init 中,重新读取属性。


Option 3 : I think, @mR_fr0g suggestion on using JMX might not be a bad idea. What you could do is :

选项 3:我认为,@mR_fr0g 关于使用 JMX 的建议可能不是一个坏主意。你可以做的是:

  1. expose your configuration bean as a MBean (read http://static.springsource.org/spring/docs/2.5.x/reference/jmx.html)
  2. Ask your admin to change the configuration properties on the MBean (or provide an interface in the bean to trigger property updates from their source)
  3. This MBean (a new piece of java code that you will need to write), MUST keep references of Beans (the ones that you want to change / inject the changed properties into). This should be simple (via setter injection or runtime fetch of bean names / classes)
    1. When the property on the MBean is changed (or triggered), it must call the appropriate setters on the respective beans. That way, your legacy code does not change, you can still manage runtime property changes.
  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 上的适当设置器。这样,您的遗留代码不会更改,您仍然可以管理运行时属性更改。

HTH!

哼!

回答by mR_fr0g

You should have a look at JMX. Spring also provides support for this.

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

回答by Gary Rowe

Further updated answer to cover scripted bean

进一步更新的答案以涵盖脚本化 bean

Another approach supported by spring 2.5.x+ is that of the scripted bean. You can use a variety of languages for your script - BeanShell is probably the most intuitive given that it has the same syntax as Java, but it does require some external dependencies. However, the examples are in Groovy.

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

Section 24.3.1.2 of the Spring Documentationcovers how to configure this, but here are some salient excerpts illustrating the approach which I've edited to make them more applicable to your situation:

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>

With the Groovy script looking like this:

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
    }
}

As the system administrator wants to make changes then they (or you) can edit the contents of the script appropriately. The script is not part of the deployed application and can reference a known file location (or one that is configured through a standard PropertyPlaceholderConfigurer during startup).

当系统管理员想要进行更改时,他们(或您)可以适当地编辑脚本的内容。该脚本不是已部署应用程序的一部分,可以引用已知文件位置(或在启动期间通过标准 PropertyPlaceholderConfigurer 配置的文件位置)。

Although the example uses a Groovy class, you could have the class execute code that reads a simple properties file. In that manner, you never edit the script directly, just touch it to change the timestamp. That action then triggers the reload, which in turn triggers the refresh of properties from the (updated) properties file, which finally updates the values within the Spring context and off you go.

尽管该示例使用 Groovy 类,但您可以让该类执行读取简单属性文件的代码。以这种方式,您永远不会直接编辑脚本,只需触摸它即可更改时间戳。然后该操作触发重新加载,这反过来触发(更新的)属性文件中的属性刷新,最终更新 Spring 上下文中的值,然后您就可以开始了。

The documentation does point out that this technique doesn't work for constructor-injection, but maybe you can work around that.

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

Updated answer to cover dynamic property changes

更新答案以涵盖动态属性更改

Quoting from this article, which provides full source code, one approach is:

从这篇文章引用,它提供了完整的源代码,一种方法是:

* 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

The observer pattern is implemented by the interfaces and classes ReloadableProperties, ReloadablePropertiesListener, PropertiesReloadedEvent, and ReloadablePropertiesBase. None of them are especially exciting, just normal listener handling. The class DelegatingProperties serves to transparently exchange the current properties when properties are updated. We only update the whole property map at once, so that the application can avoid inconsistent intermediate states (more on this later).

Now the ReloadablePropertiesFactoryBean can be written to create a ReloadableProperties instance (instead of a Properties instance, as the PropertiesFactoryBean does). When prompted to do so, the RPFB checks file modification times, and if necessary, updates its ReloadableProperties. This triggers the observer pattern machinery.

In our case, the only listener is the ReloadingPropertyPlaceholderConfigurer. It behaves just like a standard spring PropertyPlaceholderConfigurer, except that it tracks all usages of placeholders. Now when properties are reloaded, all usages of each modified property are found, and the properties of those singleton beans are assigned again.

* 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 的属性。

Original answer below covering static property changes:

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

Sounds like you just want to inject external properties into your Spring context. The PropertyPlaceholderConfigureris designed for this purpose:

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

you then reference the external properties with Ant syntax placeholders (that can be nested if you want from Spring 2.5.5 onwards)

然后使用 Ant 语法占位符引用外部属性(如果需要,从 Spring 2.5.5 开始可以嵌套)

  <bean id="example" class="org.example.DataSource">
    <property name="password" value="${password}"/>
  </bean>

You then ensure that the application.properties file is only accessible to the admin user and the user running the application.

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

Example application.properties:

示例 application.properties:

password=Aardvark

密码=土豚

回答by Sean Patrick Floyd

Or you could use the approach from this similar questionand hence also my solution:

或者你可以使用这个类似问题的方法,因此也是我的解决方案

The approach is to have beans that are configured via property files and the solution is to either

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

  • refresh the entire applicationContext (automatically using a scheduled task or manually using JMX) when properties have changed or
  • use a dedicated property provider object to access all properties. This property provider will keep checking the properties files for modification. For beans where prototype-based property lookup is impossible, register a custom eventthat your property provider will fire when it finds an updated property file. Your beans with complicated lifecycles will need to listen for that event and refresh themselves.
  • 当属性发生变化或
  • 使用专用的属性提供程序对象来访问所有属性。此属性提供程序将不断检查属性文件以进行修改。对于无法进行基于原型的属性查找的 bean,请注册一个自定义事件,您的属性提供程序在找到更新的属性文件时将触发该事件。具有复杂生命周期的 bean 将需要侦听该事件并自行刷新。

回答by Justin

Here is how I have done it in the past: running services which depend on configuration which can be changed on the fly implement a lifecycle interface: IRefreshable:

这是我过去的做法:运行依赖于可以动态更改的配置的服务实现生命周期接口: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);
}

Controllers (or services) which can modify a piece of configuration broadcast to a JMS topic that the configuration has changed (supplying the name of the configuration object). A message driven bean then invokes the IRefreshable interface contract on all beans which implement IRefreshable.

可以修改配置的控制器(或服务)广播到配置已更改的 JMS 主题(提供配置对象的名称)。然后消息驱动的 bean 调用所有实现 IRefreshable 的 bean 上的 IRefreshable 接口契约。

The nice thing with spring is that you can automatically detect any service in your application context that needs to be refreshed, removing the need to explicitly configure them:

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);
   }
  }
 }
}

This approach works particularly well in a clustered application where one of many app servers might change the configuration, which all then need to be aware of. If you want to use JMX as the mechanism for triggering the changes, your JMX bean can then broadcast to the JMS topic when any of its attributes are changed.

这种方法在集群应用程序中特别有效,其中许多应用程序服务器之一可能会更改配置,然后所有这些都需要注意。如果您想使用 JMX 作为触发更改的机制,那么您的 JMX bean 可以在其任何属性更改时广播到 JMS 主题。

回答by Adisesha

This is not something I tried, I am trying to provide pointers.

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

Assuming your application context is a subclass of AbstractRefreshableApplicationContext(example XmlWebApplicationContext, ClassPathXmlApplicationContext). AbstractRefreshableApplicationContext.getBeanFactory() will give you instance of ConfigurableListableBeanFactory. Check if it is instance of BeanDefinitionRegistry. If so you can call 'registerBeanDefinition' method. This approach will be tightly coupled with Spring implementation,

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

Check the code of AbstractRefreshableApplicationContext and DefaultListableBeanFactory(this is the implementation you get when you call 'AbstractRefreshableApplicationContext getBeanFactory()')

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

回答by Julio

You may want to have a look at the Spring Inspectora plug-gable component that provides programmatic access to any Spring based application at run-time. You can use Javascript to change configurations or manage the application behaviour at run-time.

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

回答by Hans-Peter St?rr

Hereis the nice idea of writing your own PlaceholderConfigurer that tracks the usage of properties and changes them whenever a configuration change occurs. This has two disadvantages, though:

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

  1. It does not work with constructor injection of property values.
  2. You can get race conditions if the reconfigured bean receives a changed configuration while it is processing some stuff.
  1. 它不适用于属性值的构造函数注入。
  2. 如果重新配置的 bean 在处理某些内容时收到更改的配置,您可能会遇到竞争条件。

回答by Hans-Peter St?rr

You can create a custom scope called "reconfigurable" into the ApplicationContext. It creates and caches instances of all beans in this scope. On a configuration change it clears the cache and re-creates the beans on first access with the new configuration. For this to work you need to wrap all instances of reconfigurable beans into an AOP scoped proxy, and access the configuration values with Spring-EL: put a map called configinto the ApplicationContext and access the configuration like #{ config['key'] }.

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