Estoy usando anotaciones para configurar mi entorno de primavera de esta manera:
@Configuration
...
@PropertySource("classpath:/config/default.properties")
...
public class GeneralApplicationConfiguration implements WebApplicationInitializer
{
@Autowired
Environment env;
}
Esto lleva a que mis propiedades default.properties
formen parte del Environment
. Quiero usar el @PropertySource
mecanismo aquí, porque ya brinda la posibilidad de sobrecargar propiedades a través de varias capas de respaldo y diferentes ubicaciones dinámicas, según la configuración del entorno (por ejemplo, config_dir location). Simplemente eliminé la alternativa para facilitar el ejemplo.
Sin embargo, mi problema ahora es que quiero configurar, por ejemplo, las propiedades de mi fuente de datos en default.properties
. Puede pasar la configuración a la fuente de datos sin saber en detalle qué configuración espera usar la fuente de datos
Properties p = ...
datasource.setProperties(p);
Sin embargo, el problema es que el Environment
objeto no es ni un Properties
objeto Map
ni nada comparable. Desde mi punto de vista, es simplemente no es posible acceder a todos los valores del medio ambiente, ya que no hay keySet
o iterator
método o algo comparable.
Properties p <=== Environment env?
¿Me estoy perdiendo de algo? ¿Es posible acceder a todas las entradas del Environment
objeto de alguna manera? Si es así, podría asignar las entradas a un objeto Map
u Properties
, incluso podría filtrarlas o asignarlas por prefijo: crear subconjuntos como un java estándar Map
... Esto es lo que me gustaría hacer. ¿Alguna sugerencia?
applicationContext.getEnvironment().getProperty(key)
para resolverlasEsta es una pregunta antigua, pero la respuesta aceptada tiene un grave defecto. Si el
Environment
objeto Spring contiene algún valor primordial (como se describe en Configuración externa ), no hay garantía de que el mapa de valores de propiedad que produce coincida con los devueltos por elEnvironment
objeto. Descubrí que simplemente iterar a través de laPropertySource
s de laEnvironment
no, de hecho, daba valores primordiales. En su lugar, produjo el valor original, el que debería haberse anulado.Aquí tienes una mejor solución. Esto usa la
EnumerablePropertySource
s deEnvironment
para iterar a través de los nombres de propiedad conocidos, pero luego lee el valor real del entorno Spring real. Esto garantiza que el valor es el que realmente resolvió Spring, incluidos los valores primarios.Properties props = new Properties(); MutablePropertySources propSrcs = ((AbstractEnvironment) springEnv).getPropertySources(); StreamSupport.stream(propSrcs.spliterator(), false) .filter(ps -> ps instanceof EnumerablePropertySource) .map(ps -> ((EnumerablePropertySource) ps).getPropertyNames()) .flatMap(Arrays::<String>stream) .forEach(propName -> props.setProperty(propName, springEnv.getProperty(propName)));
fuente
collect
método de la corriente en vez de hacer unforEach
:.distinct().collect(Collectors.toMap(Function.identity(), springEnv::getProperty))
. Si necesita recopilarlo en Propiedades en lugar de en Mapa, puede usar la versión de cuatro argumentos decollect
.springEnv
? ¿De dónde viene? ¿Difiere de laenv
solución aceptada?springEnv
es elenv
objeto de la pregunta original y la solución aceptada. Supongo que debería haber mantenido el mismo nombre.ConfigurableEnvironment
y no tener que hacer el yeso.Tenía el requisito de recuperar todas las propiedades cuya clave comienza con un prefijo distinto (por ejemplo, todas las propiedades que comienzan con "log4j.appender") y escribí el siguiente código (usando streams y lamdas de Java 8).
public static Map<String,Object> getPropertiesStartingWith( ConfigurableEnvironment aEnv, String aKeyPrefix ) { Map<String,Object> result = new HashMap<>(); Map<String,Object> map = getAllProperties( aEnv ); for (Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); if ( key.startsWith( aKeyPrefix ) ) { result.put( key, entry.getValue() ); } } return result; } public static Map<String,Object> getAllProperties( ConfigurableEnvironment aEnv ) { Map<String,Object> result = new HashMap<>(); aEnv.getPropertySources().forEach( ps -> addAll( result, getAllProperties( ps ) ) ); return result; } public static Map<String,Object> getAllProperties( PropertySource<?> aPropSource ) { Map<String,Object> result = new HashMap<>(); if ( aPropSource instanceof CompositePropertySource) { CompositePropertySource cps = (CompositePropertySource) aPropSource; cps.getPropertySources().forEach( ps -> addAll( result, getAllProperties( ps ) ) ); return result; } if ( aPropSource instanceof EnumerablePropertySource<?> ) { EnumerablePropertySource<?> ps = (EnumerablePropertySource<?>) aPropSource; Arrays.asList( ps.getPropertyNames() ).forEach( key -> result.put( key, ps.getProperty( key ) ) ); return result; } // note: Most descendants of PropertySource are EnumerablePropertySource. There are some // few others like JndiPropertySource or StubPropertySource myLog.debug( "Given PropertySource is instanceof " + aPropSource.getClass().getName() + " and cannot be iterated" ); return result; } private static void addAll( Map<String, Object> aBase, Map<String, Object> aToBeAdded ) { for (Entry<String, Object> entry : aToBeAdded.entrySet()) { if ( aBase.containsKey( entry.getKey() ) ) { continue; } aBase.put( entry.getKey(), entry.getValue() ); } }
Tenga en cuenta que el punto de partida es el ConfigurableEnvironment que puede devolver los PropertySources incrustados (el ConfigurableEnvironment es un descendiente directo de Environment). Puede cablearlo automáticamente mediante:
@Autowired private ConfigurableEnvironment myEnv;
Si no usa tipos muy especiales de fuentes de propiedades (como JndiPropertySource, que generalmente no se usa en la configuración automática de primavera), puede recuperar todas las propiedades que se encuentran en el entorno.
La implementación se basa en el orden de iteración que proporciona el propio resorte y toma la primera propiedad encontrada, todas las propiedades encontradas posteriormente con el mismo nombre se descartan. Esto debería garantizar el mismo comportamiento que si al entorno se le pidiera directamente una propiedad (devolviendo la primera encontrada).
Tenga en cuenta también que las propiedades devueltas aún no se resuelven si contienen alias con el operador $ {...}. Si desea que se resuelva una clave en particular, debe preguntar al entorno directamente nuevamente:
myEnv.getProperty( key );
fuente
NullPointerException
en mis pruebas unitarias cuando intenta obtener la@Autowired
instancia deConfigurationEnvironment
.La pregunta original insinuaba que sería bueno poder filtrar todas las propiedades según un prefijo. Acabo de confirmar que esto funciona a partir de Spring Boot 2.1.1.RELEASE, para
Properties
oMap<String,String>
. Estoy seguro de que ha funcionado durante un tiempo. Curiosamente, no funciona sin laprefix =
calificación, es decir, no sé cómo cargar todo el entorno en un mapa. Como dije, esto podría ser realmente con lo que OP quería comenzar. El prefijo y el siguiente '.' será despojado, que podría ser o no lo que uno quiere:@ConfigurationProperties(prefix = "abc") @Bean public Properties getAsProperties() { return new Properties(); } @Bean public MyService createService() { Properties properties = getAsProperties(); return new MyService(properties); }
Posdata: De hecho, es posible, y vergonzosamente fácil, obtener todo el entorno. No sé cómo se me escapó esto:
@ConfigurationProperties @Bean public Properties getProperties() { return new Properties(); }
fuente
getAsProperties()
siempre devuelve unaProperties
instancia vacía , e intentarlo sin un prefijo especificado ni siquiera permite que se compile. Esto es con Spring Boot 2.1.6.RELEASEComo el boleto de Jira de esta primavera , es un diseño intencional. Pero el siguiente código funciona para mí.
public static Map<String, Object> getAllKnownProperties(Environment env) { Map<String, Object> rtn = new HashMap<>(); if (env instanceof ConfigurableEnvironment) { for (PropertySource<?> propertySource : ((ConfigurableEnvironment) env).getPropertySources()) { if (propertySource instanceof EnumerablePropertySource) { for (String key : ((EnumerablePropertySource) propertySource).getPropertyNames()) { rtn.put(key, propertySource.getProperty(key)); } } } } return rtn; }
fuente
Spring no permitirá desvincularse
java.util.Properties
de Spring Environment.Pero
Properties.load()
todavía funciona en una aplicación de arranque Spring:Properties p = new Properties(); try (InputStream is = getClass().getResourceAsStream("/my.properties")) { p.load(is); }
fuente
Las otras respuestas han señalado la solución para la mayoría de los casos que involucran
PropertySources
, pero ninguna ha mencionado que ciertas fuentes de propiedad no se pueden convertir en tipos útiles.Un ejemplo es la fuente de propiedades para argumentos de línea de comando. La clase que se utiliza es
SimpleCommandLinePropertySource
. Esta clase privada es devuelta por un método público , por lo que es extremadamente complicado acceder a los datos dentro del objeto. Tuve que usar la reflexión para leer los datos y eventualmente reemplazar la fuente de la propiedad.Si alguien tiene una solución mejor, realmente me gustaría verla; sin embargo, este es el único truco en el que me he puesto a trabajar.
fuente
Trabajando con Spring Boot 2, necesitaba hacer algo similar. La mayoría de las respuestas anteriores funcionan bien, solo tenga en cuenta que en varias fases de los ciclos de vida de la aplicación los resultados serán diferentes.
Por ejemplo, después de una,
ApplicationEnvironmentPreparedEvent
las propiedades del interiorapplication.properties
no están presentes. Sin embargo, después de unApplicationPreparedEvent
evento lo son.fuente
Para Spring Boot, la respuesta aceptada sobrescribirá las propiedades duplicadas con las de menor prioridad . Esta solución recopilará las propiedades en a
SortedMap
y tomará solo las propiedades duplicadas de mayor prioridad.final SortedMap<String, String> sortedMap = new TreeMap<>(); for (final PropertySource<?> propertySource : env.getPropertySources()) { if (!(propertySource instanceof EnumerablePropertySource)) continue; for (final String name : ((EnumerablePropertySource<?>) propertySource).getPropertyNames()) sortedMap.computeIfAbsent(name, propertySource::getProperty); }
fuente
Pensé que agregaría una forma más. En mi caso proporciono esto al
com.hazelcast.config.XmlConfigBuilder
que solo necesitajava.util.Properties
resolver algunas propiedades dentro del archivo de configuración XML de Hazelcast, es decir, solo llama algetProperty(String)
método. Entonces, esto me permitió hacer lo que necesitaba:@RequiredArgsConstructor public class SpringReadOnlyProperties extends Properties { private final org.springframework.core.env.Environment delegate; @Override public String getProperty(String key) { return delegate.getProperty(key); } @Override public String getProperty(String key, String defaultValue) { return delegate.getProperty(key, defaultValue); } @Override public synchronized String toString() { return getClass().getName() + "{" + delegate + "}"; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; SpringReadOnlyProperties that = (SpringReadOnlyProperties) o; return delegate.equals(that.delegate); } @Override public int hashCode() { return Objects.hash(super.hashCode(), delegate); } private void throwException() { throw new RuntimeException("This method is not supported"); } //all methods below throw the exception * override all methods * }
PD: Terminé no usando esto específicamente para Hazelcast porque solo resuelve propiedades para archivos XML pero no en tiempo de ejecución. Como también uso Spring, decidí usar un archivo
org.springframework.cache.interceptor.AbstractCacheResolver#getCacheNames
. Esto resuelve propiedades para ambas situaciones, al menos si usa propiedades en nombres de caché.fuente