首页 文章

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

提问于
浏览
67

请考虑以下情形 . 我有一个Spring应用程序上下文,其bean属性应该是可配置的,想想 DataSourceMailSender . 可变应用程序配置由一个单独的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 回答

  • 2

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

  • 1

    以下是我过去的工作方式:运行依赖于配置的服务,可以动态更改实现生命周期界面: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接口 Contract .

    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主题 .

  • 11

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

  • 28

    Further updated answer to cover scripted 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上下文中的值,然后关闭 .

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

    Updated answer to cover dynamic property changes

    Quoting from this article,其中provides full source code,一个方法是:

    *检测文件系统更改的工厂bean
    *属性的观察者模式,以便可以传播文件系统更改
    *属性占位符配置器,用于记住使用哪个占位符,并更新单例bean的属性
    *定时器,触发定期检查已更改的文件
    观察者模式由接口和类ReloadableProperties,ReloadablePropertiesListener,PropertiesReloadedEvent和ReloadablePropertiesBase实现 . 它们都不是特别令人兴奋,只是正常的听众处理 . DelegatingProperties类用于在更新属性时透明地交换当前属性 . 我们只会立即更新整个属性映射,以便应用程序可以避免不一致的中间状态(稍后会详细介绍) . 现在可以编写ReloadablePropertiesFactoryBean来创建ReloadableProperties实例(而不是Properties实例,就像PropertiesFactoryBean那样) . 当提示执行此操作时,RPFB会检查文件修改时间,并在必要时更新其ReloadableProperties . 这会触发观察者模式机制 . 在我们的例子中,唯一的监听器是ReloadingPropertyPlaceholderConfigurer . 它的行为就像一个标准的Spring PropertyPlaceholderConfigurer,除了它跟踪占位符的所有用法 . 现在,当重新加载属性时,将找到每个已修改属性的所有用法,并再次分配这些单例bean的属性 .

    Original answer below covering static property changes:

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

    密码=土豚

  • 1

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

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

    当属性发生变化时,

    • 刷新整个applicationContext(自动使用计划任务或手动使用JMX)

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

  • 0

    这不是我试过的东西,我试图提供指针 .

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

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

  • 0

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

  • 9

    选项1 :

    • configurable bean注入 DataSourceMailSender . 始终从这些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!

  • 0

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

  • 2

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

    • 它不适用于构造函数注入属性值 .

    • 如果重新配置的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而没有高复杂性 .

相关问题