2010-10-28 19 views
66

Tenga en cuenta la situación siguiente. Tengo un contexto de aplicación Spring con un bean cuyas propiedades deberían ser configurables, piense en DataSource o MailSender. La configuración de la aplicación mutable es administrada por un bean separado, llamémoslo configuration.¿Puedo reemplazar una definición de Spring Bean en tiempo de ejecución?

Un administrador ahora puede cambiar los valores de configuración, como la dirección de correo electrónico o la URL de la base de datos, y me gustaría reinicializar el bean configurado en tiempo de ejecución.

Supongo que no puedo simplemente modificar la propiedad del bean configurable anterior (por ejemplo, creado por FactoryBean o la inyección del constructor), sino que tengo que volver a crear el bean.

¿Alguna idea sobre cómo lograr esto? Me gustaría recibir consejos sobre cómo organizar toda la configuración también. Nada está arreglado. :-)

EDITAR

Para aclarar un poco las cosas: No estoy pidiendo cómo actualizar la configuración o cómo inyectar valores de configuración estáticos. Voy a tratar de un ejemplo:

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

Así que hay un grano de constructorInjectedBean que utiliza la inyección de constructor. Imagine que la construcción del bean es muy cara, por lo que no es una opción usar un alcance de prototipo o un proxy de fábrica, piense en DataSource.

Lo que quiero hacer es que cada vez que la configuración se actualiza (a través de configurationService el grano constructorInjectedBean se recrea y se re-inyecta en el contexto de aplicación y frijoles dependientes.

Podemos suponer con seguridad que constructorInjectedBean es utilizando una interfaz tan mágico proxy es de hecho una opción

espero haber hecho la pregunta un poco más clara

+0

Entonces, ¿el bean 'configuration' necesita ser actualizado en tiempo de ejecución, o cada vez que el administrador cambia los valores? ¿Esa es tu pregunta? ¿O desea que los beans 'DataSource' /' MailSender' usen la configuración actualizada en tiempo de ejecución? ¿O son ambos? – madhurtanwani

+0

Es el segundo: quiero actualizar los valores de configuración inyectados en el tiempo de ejecución (ver editar en OP). –

Respuesta

9

Puedo pensar en un enfoque 'holder bean' (esencialmente un decorador), donde el holder bean delega en holdee, y es el bean holder el que se inyecta como una dependencia de otros beans. Nadie más tiene una referencia a holdee, pero el titular.Ahora, cuando se cambia la configuración del bean del titular, se recrea el holdee con esta nueva configuración y comienza a delegarlo.

+0

Abrí esta página solo para encontrar esta respuesta. – msangel

+1

Sepa qué, después de casi un año encuentro esta respuesta y refleja cómo finalmente implementé el problema. El decorador implementa la interfaz del objeto requerido, se registra como un observador en el servicio de configuración y vuelve a crear el objeto en cuestión si es necesario. –

+0

@Philipp Jardas gracias por tomarte el tiempo de regresar y dar una actualización con la implementación real que elijas. ¡Muy apreciado! – shrini1000

0

opción 1:..

  1. Inyecte el configurable bean en el DataSource o MailSender. Obtenga siempre los valores configurables del bean de configuración desde dentro de estos beans.
  2. Dentro del bean configurable, ejecute un hilo para leer las propiedades externamente configurables (archivo, etc.) periódicamente. De esta forma, el bean configurable se actualizará después de que el administrador haya cambiado las propiedades, por lo que el DataSource obtendrá los valores actualizados automáticamente.

Opción 2 (malo, creo, pero tal vez no - depende de caso de uso):

  1. Siempre cree nuevos beans para los beans del tipo DataSource/MailSender - usando el alcance prototype. En el init del bean, lee las propiedades de nuevo.

Opción 3: creo, @ mR_fr0g sugerencia sobre el uso de JMX podría no ser una mala idea. Lo que podría hacer es:

  1. exponga el grano de configuración como un MBean (leer http://static.springsource.org/spring/docs/2.5.x/reference/jmx.html)
  2. solicitar al administrador que cambiar las propiedades de configuración en el MBean (o proporcionar una interfaz en el grano para desencadenar cambios de propiedad de su fuente)
  3. Este MBean (una nueva pieza de código java que tendrá que escribir), DEBE conservar referencias de Beans (las que desea cambiar/inyectar las propiedades modificadas en). Esto debería ser simple (a través de inyección setter o búsqueda en tiempo de ejecución de nombres/clases de bean)
    1. Cuando la propiedad en el MBean se cambia (o se desencadena), debe llamar a los ajustadores apropiados en los beans respectivos. De esta forma, su código heredado no cambia, aún puede administrar los cambios de propiedad de tiempo de ejecución.

HTH!

+0

También espero que haya leído http://stackoverflow.com/questions/2008175/apply-dynamic-properties-to-a-bean-at-runtime – madhurtanwani

+0

Opción 1 probablemente no funcionará si no tengo control sobre las clases configuradas. Y si tuviera control sobre ellos, estarían "contaminados" por un código de configuración que no me parece bien. La opción 2 no es una opción en mi caso porque el ciclo de vida del frijol es caro. No querrá aplicar un ámbito de prototipo a un DataSource ... consulte OP para una edición con aclaraciones. –

+0

El referenced question no parece contener una respuesta a mi problema ... –

11

Deberías echar un vistazo a JMX. Spring también brinda soporte para esto.

+0

Gracias por la sugerencia. Tengo que admitir que Soy bastante novato en lo que respecta a JMX. ¿Podrías dar una pequeña pista sobre cómo lograr el comportamiento que estoy buscando? –

+0

@Philipp: JMX con Spring es increíblemente fácil. Haces algunos métodos tipo javabean (es decir, , obtener/establecer pares) que le permiten manipular el valor que desea sintonizar, y anotar la clase con '@ ManagedResource' y los métodos de bean con' @ ManagedAttribute'. Luego podrá conectarse a la instancia en ejecución con un cliente JMX (yo uso 'jvisualvm' con complementos apropiados). –

+0

@Donal: gracias por la respuesta. Sin embargo, todavía no puedo ver cómo podría manejar el problema que he descrito anteriormente. ¿Cómo ayudaría la administración de JMX a cambiar las instancias de las clases en el contexto de mi aplicación? –

2

respuesta más actualizado para cubrir con guión bean

Otro enfoque apoyado por 2.5.x resorte + es el de la haba de guión. Puede utilizar una variedad de idiomas para su secuencia de comandos: BeanShell es probablemente el más intuitivo dado que tiene la misma sintaxis que Java, pero requiere algunas dependencias externas. Sin embargo, los ejemplos están en Groovy.

Sección 24.3.1.2 de la Spring Documentation cubre cómo configurar esto, pero aquí hay algunos extractos más destacados que ilustran el enfoque que yo he editado para que sean más aplicables a su situación:

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

Con el maravilloso guión con este aspecto:

package org.example 

class GroovyMessenger implements Messenger { 

    private String message = "anotherProperty"; 

    public String getMessage() { 
     return message; 
    } 

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

como administrador del sistema quiere hacer cambios, entonces ellos (o usted) pueden editar el contenido de la escritura de manera apropiada. La secuencia de comandos no es parte de la aplicación implementada y puede hacer referencia a una ubicación de archivo conocida (o una que esté configurada mediante un PropertyPlaceholderConfigurer estándar durante el inicio).

Aunque el ejemplo usa una clase Groovy, puede hacer que la clase ejecute código que lea un archivo de propiedades simple. De esa manera, nunca edita el script directamente, simplemente tóquelo para cambiar la marca de tiempo. A continuación, esa acción desencadena la recarga, que a su vez desencadena la actualización de las propiedades del archivo de propiedades (actualizado), que finalmente actualiza los valores en el contexto de Spring y listo.

La documentación señala que esta técnica no funciona para la inyección de constructores, pero tal vez pueda solucionarlo.

respuesta Actualización para cubrir propiedad dinámica cambia

Quoting from this article, que provides full source code, un enfoque es:

* 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 

El patrón de observador es implementado por las interfaces y clases ReloadableProperties, PropiedadesRecargablesListener, PropertiesReloadedEvent y Propiedades recargablesBase. Ninguno de ellos es especialmente emocionante, solo normal oyente manejo. La clase DelegatingProperties sirve para intercambiar de forma transparente las propiedades actuales cuando las propiedades están actualizadas. Solo actualizamos la totalidad del mapa de la propiedad a la vez, de modo que la aplicación puede evitar estados intermedios inconsistentes (más sobre esto más adelante).

Ahora los ReloadablePropertiesFactoryBean puede haber escrita para crear una instancia de ReloadableProperties (en lugar de una instancia propiedades, como lo hace el PropertiesFactoryBean). Cuando se le solicite , el RPFB comprueba los tiempos de modificación del archivo , y si es necesario , actualiza sus Propiedades recargables. Esto desencadena la maquinaria del patrón observador.

En nuestro caso, el único oyente es ReloadingPropertyPlaceholderConfigurer. Se comporta como un resorte estándar PropertyPlaceholderConfigurer, excepto que rastrea todos los usos de marcadores de posición. Ahora cuando se vuelven a cargar las propiedades , se encuentran todos los usos de cada propiedad modificada, y las propiedades de esos beans singleton tienen asignado nuevamente.

Respuesta original por debajo cubriendo cambios en las propiedades estáticas:

Suena como lo que desea inyectar propiedades externas en su contexto primavera. El PropertyPlaceholderConfigurer está diseñado para este fin:

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

a continuación referencia a las propiedades externas con marcadores de posición de sintaxis (Ant que se pueden anidar si quieres de primavera 2.5.5 en adelante)

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

A continuación, asegúrese que el archivo application.properties solo es accesible para el usuario administrador y el usuario que ejecuta la aplicación.

Ejemplo de aplicación.propiedades:

password = Aardvark

+0

Gracias por el Respuesta. De hecho, estoy preguntando cómo actualizar las propiedades en tiempo de ejecución (vea editar en OP). –

+0

@Philipp OK, en ese caso, probablemente esta discusión sea útil (es para una versión anterior de Spring, pero podría actualizarse). Lee el ex comentarios tensos para notas sobre cómo hacer que funcione con entradas de mapas y similares: http://www.wuenschenswert.net/wunschdenken/archives/127 –

+0

@Gary, gracias por la respuesta. Revisé el blog que mencionaste y descubrí que ya había creado algo como esto yo mismo. Sin embargo, todavía no tengo idea de cómo podría no solo actualizar las propiedades de los beans, sino también reemplazar las instancias de bean. Supongamos por cuestiones de discusión que estamos hablando de una propiedad de un 'DataSource' que se crea con un' FactoryBean'. El enfoque mencionado solo actualizaría los valores del bean de fábrica, lo cual no es de ninguna ayuda. :-( –

2

O usted podría utilizar el enfoque de this similar question y por lo tanto también my solution:

El enfoque es tener los granos que se configuran a través de archivos de propiedades y la solución es bien

  • actualice toda la aplicaciónContext (automáticamente usando una tarea programada o manualmente usando JMX) cuando las propiedades han cambiado o
  • usa un objeto de proveedor de propiedades dedicado para acceder a todas las propiedades. Este proveedor de propiedad seguirá revisando los archivos de propiedades para su modificación. Para los frijoles donde la búsqueda de propiedades basada en prototipos es imposible, register a custom event que su proveedor de propiedad activará cuando encuentre un archivo de propiedad actualizado. Sus frijoles con ciclos de vida complicados necesitarán escuchar ese evento y refrescarse.
28

Así es como lo he hecho en el pasado: los servicios en ejecución que dependen de la configuración que se pueden cambiar sobre la marcha implementar una interfaz de ciclo de vida: 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); 
} 

controladores (o servicios), que puede modificar una parte de la transmisión de configuración a un tema JMS que la configuración ha cambiado (proporcionando el nombre del objeto de configuración). Un bean controlado por mensaje invoca el contrato de interfaz IRefreshable en todos los beans que implementan IRefreshable.

Lo bueno con la primavera es que se puede detectar automáticamente cualquier servicio en su contexto de aplicación que necesita ser refrescado, eliminando la necesidad de configurar de forma explícita:

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

Este enfoque funciona particularmente bien en un clúster aplicación donde uno de los muchos servidores de aplicaciones podría cambiar la configuración, que todos deben conocer. Si desea utilizar JMX como mecanismo para activar los cambios, su bean JMX puede luego transmitir al tema JMS cuando se modifique cualquiera de sus atributos.

+0

También debo mencionar que al usar JMX debe hacer un trabajo adicional para lograr que la seguridad de JMX se delegue en el modelo de seguridad de su aplicación. Con el enfoque anterior, no es un problema ya que el cambio de configuración se realiza utilizando su GUI web (suponiendo que tenga una) reutilizando su modelo de seguridad de la aplicación existente. Dado que la actualización simplemente se 'sugiere' a través de JMS, en realidad no es necesario asegurarla (para este propósito). – Justin

+0

El problema con este enfoque es que la actualización debe ser una operación atómica con respecto a otras solicitudes procesadas por la aplicación. De lo contrario, una solicitud se procesaría con una configuración modificada en el medio de su procesamiento. Esto puede causar un comportamiento impredecible. Esencialmente, tendría que bloquear las solicitudes que utilizan el contexto de la aplicación de alguna manera. Pero de lo que puede actualizar el contexto de la aplicación. – SpaceTrucker

1

Esto no es algo que haya intentado, estoy tratando de proporcionar punteros.

Suponiendo que el contexto de su aplicación es una subclase de AbstractRefreshableApplicationContext (ejemplo XmlWebApplicationContext, ClassPathXmlApplicationContext). AbstractRefreshableApplicationContext.getBeanFactory() le dará instancia de ConfigurableListableBeanFactory. Compruebe si es una instancia de BeanDefinitionRegistry. Si es así puedes llamar al método 'registerBeanDefinition'. Este enfoque será estrechamente unida con la implementación de primavera,

Comprobar el código de AbstractRefreshableApplicationContext y DefaultListableBeanFactory (esta es la aplicación que se obtiene cuando se llama a 'AbstractRefreshableApplicationContext getBeanFactory()')

+0

Esto podría ser una buena idea. La interfaz para 'registerBeanDefinition()' es 'org.springframework.beans.factory.support.BeanDefinitionRegistry', por cierto. Voy a ver esto, gracias. –

+0

Sí. Tampoco olvide configurar 'allowBeanDefinitionOverriding' en el contexto de la aplicación. – Adi

0

Es posible que desee echar un vistazo a el Spring Inspector un componente plug-gable que proporciona acceso programático a cualquier aplicación basada en Spring en tiempo de ejecución. Puede usar Javascript para cambiar configuraciones o administrar el comportamiento de la aplicación en tiempo de ejecución.

0

Here es la buena idea de escribir su propio PlaceholderConfigurer que rastrea el uso de las propiedades y las cambia cada vez que se produce un cambio en la configuración. Sin embargo, esto tiene dos desventajas:

  1. No funciona con la inyección de valores de propiedad del constructor.
  2. Puede obtener condiciones de carrera si el bean reconfigurado recibe una configuración modificada de mientras procesa algunas cosas.
1

Puede crear un ámbito personalizado denominado "reconfigurable" en ApplicationContext. Crea y almacena en caché instancias de todos los beans en este ámbito. En un cambio de configuración, borra la caché y vuelve a crear los beans en el primer acceso con la nueva configuración. Para que esto funcione, debe envolver todas las instancias de beans reconfigurables en un proxy con ámbito AOP y acceder a los valores de configuración con Spring-EL: coloque un mapa llamado config en ApplicationContext y acceda a la configuración como #{ config['key'] }.

0

Mi solución fue copiar el objeto original. Puño he creado una interfaz

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

Para facilitar la aplicación del puño función de crear un constructor con todos los campos, por lo que el IDEme podría ayudar un poco. Luego puede hacer un constructor de copia que use la misma función Updateable#copyObject(T originalObject). Puede también beneficiarse del código del constructor creado por el IDEpara crear la función de implementar:

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

que lo utilizaron en un controlador para la actualización de la configuración actual de la aplicación:

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

Así que el currentSettingsData que tiene la configuración de la aplicación va a tener los valores actualizados, ubicado en newSettingsData. Este método permite actualizar cualquier bean sin una gran complejidad.

Cuestiones relacionadas