java - que - spring mvc 4 tutorial español
¿Puedo reemplazar una definición de Spring Bean en tiempo de ejecución? (11)
Así es como lo hice en el pasado: la ejecución de servicios que dependen de la configuración que se puede cambiar sobre la marcha implementa 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 pueden 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 de la primavera es que puede detectar automáticamente cualquier servicio en el contexto de su aplicación que necesita actualizarse, eliminando la necesidad de configurarlos explícitamente:
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 una aplicación en clúster donde uno de los muchos servidores de aplicaciones puede cambiar la configuración, que todos deben tener en cuenta. 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.
Considere la siguiente situación. Tengo un contexto de aplicación Spring con un bean cuyas propiedades deben ser configurables, piensen DataSource
o MailSender
. La configuración de la aplicación mutable es administrada por un bean separado, llamémosle configuration
.
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.
Supongamos 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 preguntando cómo actualizar la configuración o cómo inyectar valores de configuración estáticos. Voy a intentar 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 constructorInjectedBean
beans constructorInjectedBean
que usa inyección de constructor. Imagine que la construcción del bean es muy costosa, 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 se actualice la configuración (a través de configurationService
el bean constructorInjectedBean
se vuelva a crear y vuelva a inyectar en el contexto de la aplicación y los beans dependientes.
Podemos suponer con seguridad que constructorInjectedBean
está utilizando una interfaz para que la magia del proxy sea realmente una opción.
Espero haber aclarado la pregunta un poco.
Deberías echarle un vistazo a JMX . Spring también brinda soporte para esto.
Es posible que desee echar un vistazo a 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.
Esto no es algo que intenté, estoy tratando de proporcionar punteros.
Suponiendo que su contexto de 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 estará estrechamente relacionado con la implementación de Spring,
Compruebe el código de AbstractRefreshableApplicationContext y DefaultListableBeanFactory (esta es la implementación que obtiene cuando llama a ''AbstractRefreshableApplicationContext getBeanFactory ()'')
Mi solución fue copiar el objeto original. Puño, creé 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 implementación de la función puño crear un constructor con todos los campos, por lo que el IDE podría ayudarme un poco. Luego puede hacer un constructor de copia que use la misma función Updateable#copyObject(T originalObject)
. También puede aprovechar el código del constructor creado por el IDE para crear la función a 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;
}
}
Lo usé en un controlador para actualizar 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());
}
}
Entonces el currentSettingsData
que tiene la configuración de la aplicación va a tener los valores actualizados, ubicados en newSettingsData
. Este método permite actualizar cualquier bean sin una gran complejidad.
O podría usar el enfoque de esta pregunta similar y, por lo tanto, también mi solución :
El enfoque es tener beans configurados a través de archivos de propiedades y la solución es
- actualice todo el ApplicationContext (automáticamente usando una tarea programada o manualmente usando JMX) cuando las propiedades han cambiado o
- utilizar 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, registre un evento personalizado que su proveedor de propiedad activará cuando encuentre un archivo de propiedades actualizado. Sus frijoles con ciclos de vida complicados necesitarán escuchar ese evento y refrescarse.
Opción 1 :
- Inyecte el bean
configurable
en elDataSource
oMailSender
. Obtenga siempre los valores configurables del bean de configuración desde dentro de estos beans. - Dentro del bean
configurable
ejecute un hilo para leer las propiedades externamente configurables (archivo, etc.) periódicamente. De esta forma, el beanconfigurable
se actualizará después de que el administrador haya cambiado las propiedades y, por lo tanto,DataSource
obtendrá los valores actualizados automáticamente.- No necesita implementar el "hilo" - lea: http://commons.apache.org/configuration/userguide/howto_filebased.html#Automatic_Reloading
Opción 2 (mala, creo, pero tal vez no - depende del caso de uso):
- Siempre cree beans nuevos para beans de tipo
DataSource
/MailSender
, utilizando el alcance delprototype
. En el init del bean, lee las propiedades de nuevo.
Opción 3: Creo que la sugerencia de @mR_fr0g sobre el uso de JMX podría no ser una mala idea. Lo que podrías hacer es:
- exponga su bean de configuración como un MBean (lea http://static.springsource.org/spring/docs/2.5.x/reference/jmx.html )
- Pídale a su administrador que cambie las propiedades de configuración en el MBean (o proporcione una interfaz en el bean para activar las actualizaciones de propiedad desde su fuente)
- Este MBean (una nueva pieza de código java que deberá escribir), DEBE conservar referencias de Beans (en las que desea cambiar / inyectar las propiedades modificadas). 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)
- Cuando la propiedad en el MBean se cambia (o 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!
Puede crear un ámbito personalizado llamado "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: poner un mapa llamado config
en ApplicationContext y acceder a la configuración como #{ config[''key''] }
.
Puedo pensar en un enfoque de ''titular de frijol'' (esencialmente un decorador), donde el titular bean delega en holdee, y es el grano de titular el que se inyecta como una dependencia en otros granos. 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.
Aquí está 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. Esto tiene dos desventajas, sin embargo:
- No funciona con la inyección del constructor de los valores de las propiedades.
- Puede obtener condiciones de carrera si el bean reconfigurado recibe una configuración modificada mientras procesa algunas cosas.
Otra respuesta actualizada para cubrir el bean guionado
Otro enfoque apoyado por la primavera 2.5.x + es el del bean con scripts. 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.
La Sección 24.3.1.2 de la Documentación de primavera cubre cómo configurar esto, pero aquí hay algunos extractos sobresalientes que ilustran el enfoque que 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 la secuencia de comandos Groovy como esta:
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 el administrador del sistema desea hacer cambios, ellos (o usted) pueden editar los contenidos del script 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 evitarlo.
Respuesta actualizada para cubrir cambios dinámicos de propiedad
Citando de este artículo , que proporciona el código fuente completo , 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 se implementa mediante las interfaces y clases ReloadableProperties, ReloadablePropertiesListener, PropertiesReloadedEvent y ReloadablePropertiesBase. Ninguno de ellos es especialmente emocionante, solo el manejo normal del oyente. La clase DelegatingProperties sirve para intercambiar de forma transparente las propiedades actuales cuando se actualizan las propiedades. Solo actualizamos el mapa de propiedades completo de una vez, de modo que la aplicación puede evitar estados intermedios inconsistentes (más sobre esto más adelante).
Ahora, la propiedad ReloadblePropertiesFactoryBean se puede escribir para crear una instancia de ReloadableProperties (en lugar de una instancia de Properties, como lo hace el PropertiesFactoryBean). Cuando se le solicite hacerlo, el RPFB verifica los tiempos de modificación del archivo y, si es necesario, actualiza sus Propiedades recargables. Esto activa la maquinaria de patrones de observadores.
En nuestro caso, el único oyente es ReloadingPropertyPlaceholderConfigurer. Se comporta como un PropertyPlaceholderConfigurer de primavera estándar, 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 se asignan nuevamente.
La respuesta original a continuación cubre los cambios de propiedad estática:
Parece que solo quieres inyectar propiedades externas en tu contexto Spring. El PropertyPlaceholderConfigurer
está diseñado para este propósito:
<!-- 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, hace referencia a las propiedades externas con marcadores de posición de sintaxis Ant (que pueden anidarse si lo desea desde Spring 2.5.5 en adelante)
<bean id="example" class="org.example.DataSource">
<property name="password" value="${password}"/>
</bean>
Luego, asegúrese de que solo el usuario administrador y el usuario que ejecuta la aplicación puedan acceder al archivo application.properties.
Ejemplo application.properties:
contraseña = Aardvark