Можно ли заменить определение Spring bean во время выполнения?

Рассмотрим следующий сценарий. У меня есть контекст приложения Spring с bean, свойства которого должны быть настраиваемыми, подумайте DataSource или MailSender. Конфигурирование изменчивого приложения управляется отдельным 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 очень дорога, поэтому использование области прототипа или прокси-сервера factory не является вариантом, подумайте DataSource.

Я хочу, чтобы каждый раз, когда конфигурация обновляется (через configurationService, bean constructorInjectedBean воссоздается и повторно вводится в контекст приложения и зависит от beans.

Мы можем с уверенностью предположить, что constructorInjectedBean использует интерфейс, поэтому магия прокси-сервера действительно является опцией.

Я надеюсь, что вопрос стал немного понятнее.

Ответ 1

Я могу придумать подход "держатель bean" (по сути, декоратор), где держатель bean делегирует хозяину, а это держатель bean, который вводится как зависимость в другой beans. Никто другой не имеет ссылки на держателя, но держателя. Теперь, когда изменяется конфигуратор bean, он воссоздает владельца с этой новой конфигурацией и начинает делегировать ему.

Ответ 2

Вот как я это делал в прошлом: запуск служб, которые зависят от конфигурации, которые могут быть изменены на лету, реализуют интерфейс жизненного цикла: 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 на всех beans, которые реализуют 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 при изменении любого из его атрибутов.

Ответ 4

Дальнейший обновленный ответ на обложку bean

Другой подход, поддерживаемый spring 2.5.x +, относится к сценарию bean. Вы можете использовать различные языки для вашего script - BeanShell, вероятно, наиболее интуитивно понятен, учитывая, что он имеет тот же синтаксис, что и Java, но для этого требуются некоторые внешние зависимости. Однако примеры приведены в Groovy.

В разделе 24.3.1.2 Spring Документация описывается, как настроить это, но здесь приведены некоторые существенные выдержки, иллюстрирующие подход, отредактированы, чтобы сделать их более применимыми к вашей ситуации:

<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 script выглядит следующим образом:

package org.example

class GroovyMessenger implements Messenger {

    private String message = "anotherProperty";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message
    }
}

Как системный администратор хочет внести изменения, они (или вы) могут соответствующим образом редактировать содержимое script. script не является частью развернутого приложения и может ссылаться на известное расположение файла (или тот, который настроен через стандартный PropertyPlaceholderConfigurer во время запуска).

Хотя в этом примере используется класс Groovy, вы можете получить код выполнения класса, который читает простой файл свойств. Таким образом, вы никогда не отредактируете script напрямую, просто коснитесь его, чтобы изменить метку времени. Затем это действие запускает перезагрузку, что, в свою очередь, вызывает обновление свойств из (обновленного) файла свойств, что, наконец, обновляет значения в контексте spring и выключается.

В документации указывается, что этот метод не работает для инсталляции конструктора, но, возможно, вы можете обойти это.

Обновленный ответ для изменения динамических свойств

Цитата из этой статьи, которая предоставляет полный исходный код, один подход:

* 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. Он ведет себя точно так же, как стандартный springPropertyPlaceholderConfigurer, за исключением что он отслеживает все заполнители. Теперь, когда свойства перезагрузка, все виды использования каждого модифицированного свойство найдено, а свойства этих одноэлементных beansеще раз.

Оригинальный ответ ниже, охватывающий изменения статических свойств:

Похоже, вы просто хотите вводить внешние свойства в свой контекст 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>

Затем вы убедитесь, что файл application.properties доступен только для пользователя admin и пользователя, запускающего приложение.

Пример application.properties:

пароль = Aardvark

Ответ 5

Или вы можете использовать подход этот аналогичный вопрос и, следовательно, также мое решение:

Подход состоит в том, чтобы beans настраивался через файлы свойств, и решение было либо

  • обновить весь applicationContext (автоматически используя запланированную задачу или вручную с помощью JMX) при изменении свойств или
  • используйте выделенный объект поставщика свойств для доступа ко всем свойствам. Этот поставщик свойств продолжит проверку файлов свойств для модификации. Для beans, где поиск свойств на основе прототипа невозможен, зарегистрировать настраиваемое событие, которое ваш поставщик свойств будет срабатывать, когда он найдет обновленный файл свойств, Ваш beans со сложными жизненными циклами должен будет прослушать это событие и обновить себя.

Ответ 6

Это не то, что я пытался, я пытаюсь предоставить указатели.

Предполагая, что ваш контекст приложения является подклассом AbstractRefreshableApplicationContext (пример XmlWebApplicationContext, ClassPaспасибоmlApplicationContext). AbstractRefreshableApplicationContext.getBeanFactory() предоставит вам экземпляр ConfigurableListableBeanFactory. Проверьте, является ли это экземпляром BeanDefinitionRegistry. Если это так, вы можете вызвать метод registerBeanDefinition. Этот подход будет тесно связан с реализацией Spring,

Проверьте код AbstractRefreshableApplicationContext и DefaultListableBeanFactory (это реализация, которую вы получаете при вызове 'AbstractRefreshableApplicationContext getBeanFactory()')

Ответ 7

Вы можете создать настраиваемую область, называемую "реконфигурируемой" в ApplicationContext. Он создает и кэширует экземпляры всех beans в этой области. При изменении конфигурации он очищает кеш и повторно создает beans при первом доступе с новой конфигурацией. Для этого вам необходимо обернуть все экземпляры реконфигурируемого beans в прокси-сервер с AOP и получить доступ к значениям конфигурации с помощью Spring -EL: поместить карту с именем config в ApplicationContext и получить доступ к конфигурации, например #{ config['key'] }.

Ответ 8

Вариант 1:

  • Внесите configurable bean в DataSource или MailSender. Всегда получайте конфигурируемые значения из конфигурации bean из этих beans.
  • Внутри configurable bean запустите поток, чтобы периодически считывать внешние параметры (файл и т.д.). Таким образом, configurable bean обновится после того, как администратор изменил свойства, и поэтому DataSource автоматически получит обновленные значения.

Вариант 2 (плохой, я думаю, но, возможно, нет - зависит от варианта использования):

  • Всегда создавайте новый beans для beans типа DataSource/MailSender - используя область prototype. В начале bean прочитайте свойства заново.

Вариант 3: Я думаю, что предложение @mR_fr0g по использованию JMX может быть плохой идеей. Что вы можете сделать:

  • выставьте свою конфигурацию bean как MBean (прочитайте http://static.springsource.org/spring/docs/2.5.x/reference/jmx.html)
  • Попросите администратора изменить свойства конфигурации в MBean (или предоставить интерфейс в bean для запуска обновлений свойств из их источника).
  • Этот MBean (новый фрагмент кода Java, который вам нужно будет писать), ДОЛЖЕН хранить ссылки beans (те, которые вы хотите изменить/вставить измененные свойства). Это должно быть простым (с помощью инсталляции сеттера или извлечения из bean имен/классов)
    • Когда свойство на MBean изменяется (или запускается), оно должно вызывать соответствующие сеттеры на соответствующем beans. Таким образом, ваш устаревший код не изменяется, вы все равно можете управлять изменениями среды выполнения.

НТН!

Ответ 9

Возможно, вам захочется взглянуть на Spring Inspector компонент плагинов, который обеспечивает программный доступ к любому Spring приложения во время выполнения. Вы можете использовать Javascript для изменения конфигураций или управления поведением приложения во время выполнения.

Ответ 10

Здесь - хорошая идея написать свой собственный PlaceholderConfigurer, который отслеживает использование свойств и изменяет их при каждом изменении конфигурации. Это имеет два недостатка:

  • Он не работает с вводом значений свойств конструктора.
  • Вы можете получить условия гонки, если реконфигурированный bean получит изменила конфигурацию, пока она обрабатывает некоторые вещи.

Ответ 11

Моим решением было скопировать исходный объект. Fist я создал интерфейс

/**
 * 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 создайте конструктор со всеми полями, поэтому 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;
    } 
}

Я использовал его в контроллере для обновления текущих настроек для приложения:

        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 без высокой сложности.