2010-10-28 3 views
66

Рассмотрите следующий сценарий. У меня есть контекст приложения Spring с компонентом, свойства которого должны быть настраиваемыми, подумайте DataSource или MailSender. Конфигурация изменяемого приложения управляется отдельным компонентом, назовем его configuration.Могу ли я заменить определение Spring bean во время выполнения?

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

Предположим, что я просто не могу просто изменить свойство конфигурируемого компонента выше (например, создано FactoryBean или инсталлятором конструктора), но нужно воссоздать сам компонент.

Любые мысли о том, как этого достичь? Я был бы рад получить советы о том, как организовать всю конфигурацию. Ничего не исправлено. :-)

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> 

Таким образом, есть фасоль constructorInjectedBean, который использует инъекции конструктора. Представьте себе, что конструкция боба очень дорога, поэтому использование области прототипа или заводского прокси не является вариантом, подумайте DataSource.

То, что я хочу делать то, что каждый раз, когда конфигурация обновляются (через configurationService боб constructorInjectedBean в настоящее время воссоздан и вновь закачивается в контекст приложения и зависимую фасоль.

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

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

+0

Таким образом, компонент конфигурации должен быть обновлен во время выполнения - или каждый раз, когда администратор изменяет значения? Я что твой вопрос? Или вы хотите, чтобы «DataSource' /' MailSender »использовали обновленную конфигурацию во время выполнения? Или это так? – madhurtanwani

+0

Это второе: я хочу обновить введенные значения конфигурации во время выполнения (см. Edit в OP). –

ответ

9

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

+0

Я открываю эту страницу, чтобы найти этот ответ. – msangel

+1

Знайте, что после почти одного года я нахожу этот ответ, и он отражает, как я реализовал проблему в конце. Декоратор реализует интерфейс требуемого объекта, регистрируется как наблюдатель в службе конфигурации и, если необходимо, повторно создает объект, о котором идет речь. –

+0

@Philipp Jardas благодарим вас за то, что вы нашли время, чтобы вернуться и дать обновление с фактической реализацией, которую вы выбрали. Очень признателен! – shrini1000

0

вариант 1:..

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

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

  1. Всегда создавайте новые бобы для фасоли типа DataSource/MailSender - используя prototype scope. В начале компонента, прочитайте свойства заново.

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

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

HTH!

+0

Также я надеюсь, что вы прочитали http://stackoverflow.com/questions/2008175/apply-dynamic-properties-to-a-bean-at-runtime – madhurtanwani

+0

Вариант 1, вероятно, не будет работать, если у меня нет контроля над сконфигурированные классы. И если бы у меня был контроль над ними, они были бы «загрязнены» кодом конфигурации, который звучит не очень хорошо для меня. Вариант 2 не является вариантом в моем случае, потому что жизненный цикл бобины дорог. Вы не хотели бы применять область прототипа к DataSource ... см. OP для редактирования с пояснениями. –

+0

referenced question, похоже, не отвечает на мои проблемы ... –

11

Вы должны взглянуть на JMX. Spring также обеспечивает поддержку этого.

+0

Спасибо за предложение. «Не совсем новичок, когда дело доходит до JMX. Не могли бы вы немного рассказать о том, как добиться такого поведения, которое я ищу? –

+0

@Philipp: JMX с весной довольно удивительно легко. Вы делаете некоторые javabean-подобные методы (т.е. , get/set pairs), которые позволяют вам манипулировать значением, которое вы хотите настроить, и аннотируете класс с помощью '@ ManagedResource' и методов bean с помощью' @ ManagedAttribute'. Затем вы сможете подключиться к исполняемому экземпляру с помощью клиент JMX (я использую 'jvisualvm' с соответствующие плагины). –

+0

@ Дональд: Спасибо за ответ. Тем не менее, я до сих пор не могу понять, как я мог бы решить проблему, описанную выше. Как управление JMX поможет в изменении экземпляров классов в моем контексте приложения? –

2

Далее обновленный ответ для покрытия сценария компонентов-

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

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

<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 и выключается.

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

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

Quoting from this article, который provides full source code, один подход является:

* 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, СвойстваReloadedEvent и ReloadablePropertiesBase. Ни один из них особенно интересен, просто нормальный прослушиватель. Класс DelegatingProperties служит для прозрачно обменивать текущие свойства, когда свойства обновлены. Мы только обновляем всю карту недвижимости сразу, так что приложение может избежать непоследовательности промежуточных состояний (подробнее об этом ).

Теперь ReloadablePropertiesFactoryBean может быть написана для создания экземпляра ReloadableProperties (вместо экземпляра свойства, как это делает PropertiesFactoryBean). Когда предложит это сделать, RPFB проверяет время изменения файла , и если необходимо , обновляет его ReloadableProperties. Это вызывает механизм наблюдателя.

В нашем случае единственным слушателем является ReloadingPropertyPlaceholderConfigurer. Он ведет себя точно так же, как стандартная пружина PropertyPlaceholderConfigurer, за исключением , что он отслеживает все виды использования заполнителей. Теперь, когда свойства перезагружены, все виды использования каждого модифицированного свойства найдены, и свойства этих одноэлементных бобов снова назначаются .

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

Похоже, вы просто хотите, чтобы ввести внешние свойства в ваш контекст 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 и пользователя, запускающего приложение.

Пример применения.Свойства:

пароль = Aardvark

+0

Спасибо за Ответ: Я действительно спрашиваю о том, как обновлять свойства во время выполнения (см. edit в OP). –

+0

@Philipp OK, в этом случае это обсуждение, вероятно, поможет (это для старой версии Spring, но может быть обновлено). Прочтите задумчивые комментарии для заметок о том, как заставить его работать с картографическими записями и т. п.: http://www.wuenschenswert.net/wunschdenken/archives/127 –

+0

@Gary, спасибо за ответ. Я проверил блог, о котором вы упоминали, и обнаружил, что я уже создал что-то подобное. Тем не менее, я до сих пор не знаю, как я мог не только обновлять свойства bean-компонентов, но и сам заменять экземпляры bean-компонентов. Предположим, что для обсуждения мы говорим об объекте «DataSource», который создается с помощью «FactoryBean». Указанный подход будет только обновлять значения фабричного компонента, что совсем не помогает. :-( –

2

Или вы могли бы использовать подход с this similar question и, следовательно, также my solution:

подход должен иметь бобы, которые настраиваются с помощью файлов свойств и решение либо

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

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

Хорошая вещь с весной, что вы можете автоматически обнаружить любую услугу в контексте приложения, которые должны быть обновлены, устраняя необходимость явно настроить их:

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-компонент затем может транслироваться в тему JMS при изменении любого из его атрибутов.

+0

Следует также упомянуть, что при использовании JMX вам нужно сделать дополнительную работу, чтобы обеспечить безопасность JMX для делегирования на модель безопасности вашего приложения. При использовании вышеприведенного подхода это не проблема, поскольку изменение конфигурации выполняется с помощью вашего веб-графического интерфейса (при условии, что у вас есть), повторного использования существующей модели безопасности приложения. Поскольку обновление просто «предлагается» через JMS, его действительно не нужно защищать (для этой цели). – Justin

+0

Проблема с этим подходом заключается в том, что обновление должно быть атомной операцией в отношении других запросов, обрабатываемых приложением. В противном случае запрос будет обрабатываться с измененной конфигурацией в середине обработки. Это может вызвать непредсказуемое поведение. Таким образом, вы должны блокировать запросы, которые каким-то образом используют контекст приложения. Но вы можете просто обновить сам контекст приложения. – SpaceTrucker

1

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

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

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

+0

Это может показаться неплохой идеей. Интерфейс для 'registerBeanDefinition()' - это 'org.springframework.beans.factory.support.BeanDefinitionRegistry', кстати. Я посмотрю на это, спасибо. –

+0

Да. Также не забудьте установить «allowBeanDefinitionOverriding» в контексте приложения. – Adi

0

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

0

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

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

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

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

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

 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. Этот метод позволяет обновлять любой компонент без высокой сложности.

Смежные вопросы