¿Es posible usar Spring @Value para mapear valores del archivo de propiedades al HashMap?

Actualmente tengo algo como esto, y mapear un valor no es un problema. Pero necesito mapear valores personalizados en los vencimientos de HashMap. ¿Es posible algo como esto?

@Service
@PropertySource(value = "classpath:my_service.properties")
public class SomeServiceImpl implements SomeService {


    @Value("#{conf['service.cache']}")
    private final boolean useCache = false;

    @Value("#{conf['service.expiration.[<custom name>]']}")
    private final HashMap<String, String> expirations = new HashMap<String, String>();

Archivo de propiedades: 'my_service.properties'

service.cache=true
service.expiration.name1=100
service.expiration.name2=20

¿Es posible mapear como esta clave: conjunto de valores

  • nombre1 = 100

  • nombre2 = 20

56
d-sauer 6 feb. 2015 a las 18:45

7 respuestas

La mejor respuesta

Hago una solución inspirada en la publicación anterior.

Registre el archivo de propiedades en la configuración de Spring:

<util:properties id="myProp" location="classpath:my.properties"/>

Y creo el componente:

@Component("PropertyMapper")
public class PropertyMapper {

    @Autowired
    ApplicationContext applicationContext;

    public HashMap<String, Object> startWith(String qualifier, String startWith) {
        return startWith(qualifier, startWith, false);
    }

    public HashMap<String, Object> startWith(String qualifier, String startWith, boolean removeStartWith) {
        HashMap<String, Object> result = new HashMap<String, Object>();

        Object obj = applicationContext.getBean(qualifier);
        if (obj instanceof Properties) {
            Properties mobileProperties = (Properties)obj;

            if (mobileProperties != null) {
                for (Entry<Object, Object> e : mobileProperties.entrySet()) {
                    Object oKey = e.getKey();
                    if (oKey instanceof String) {
                        String key = (String)oKey;
                        if (((String) oKey).startsWith(startWith)) {
                            if (removeStartWith) 
                                key = key.substring(startWith.length());
                            result.put(key, e.getValue());
                        }
                    }
                }
            }
        }

        return result;
    }
}

Y cuando quiero mapear todas las propiedades que comienzan con un valor específico a HashMap, con la anotación @Value:

@Service
public class MyServiceImpl implements MyService {

    @Value("#{PropertyMapper.startWith('myProp', 'service.expiration.', true)}")
    private HashMap<String, Object> portalExpirations;
16
d-sauer 9 feb. 2015 a las 15:47

Use el mismo nombre de variable que el nombre de Yaml

Eg:

private final HashMap<String, String> expiration 

En lugar de

private final HashMap<String, String> expirations 
0
Kevin Mayo 1 ago. 2020 a las 04:21

Solución para extraer Mapa usando @Value de la propiedad application.yml codificada como multilínea

application.yml

other-prop: just for demo 

my-map-property-name: "{\
         key1: \"ANY String Value here\", \  
         key2: \"any number of items\" , \ 
         key3: \"Note the Last item does not have comma\" \
         }"

other-prop2: just for demo 2 

Aquí, el valor de nuestra propiedad de mapa "my-map-property-name" se almacena en formato JSON dentro de una cadena y hemos logrado varias líneas usando \ al final de la línea

myJavaClass.java

import org.springframework.beans.factory.annotation.Value;

public class myJavaClass {

@Value("#{${my-map-property-name}}") 
private Map<String,String> myMap;

public void someRandomMethod (){
    if(myMap.containsKey("key1")) {
            //todo...
    } }

}

Más explicación

  • \ en yaml se usa para dividir la cadena en varias líneas

  • \ " es un carácter de escape para" (cita) en la cadena yaml

  • {clave: valor} JSON en yaml que será convertido a mapa por @Value

  • # {} es expresión SpEL y se puede usar en @Value para convertir json int Map o Array / list Referencia

Probado en un proyecto de arranque de primavera

2
Milan 1 may. 2020 a las 00:40

La solución basada en Spring Boot más rápida que se me ocurre es la siguiente. En mi ejemplo particular, estoy migrando datos de un sistema a otro. Es por eso que necesito un mapeo para un campo llamado prioridad .

Primero he creado el archivo de propiedades (priority-migration.properties) como tal:

my.prefix.priority.0:0
my.prefix.priority.10:1
my.prefix.priority.15:2
my.prefix.priority.20:2
another.prefix.foo:bar

Y ponerlo en classpath.

Suponiendo que desea usar el mapa en un bean / componente administrado por Spring, anote su clase con:

@Component
@PropertySource("classpath:/priority-migration.properties")

Lo que realmente desea en su mapa es, por supuesto, solo los pares clave / valor que tienen el prefijo my.prefix, es decir, esta parte:

{
    0:0
    10:1
    15:2
    20:2
}

Para lograrlo, debe anotar su componente con

@ConfigurationProperties("my.prefix")

Y cree un captador para el infijo prioridad . Este último resultó ser obligatorio en mi caso (aunque el Sring Doc dice que es suficiente tener una propiedad prioridad e inicializarla con un valor mutable)

private final Map<Integer, Integer> priorityMap = new HashMap<>();

public Map<Integer, Integer> getPriority() {
    return priorityMap;
}

Al final

Se ve algo como esto:

@Component
@ConfigurationProperties("my.prefix")
@PropertySource("classpath:/priority-migration.properties")
class PriorityProcessor {

    private final Map<Integer, Integer> priorityMap = new HashMap<>();

    public Map<Integer, Integer> getPriority() {
        return priorityMap;
    }

    public void process() {

        Integer myPriority = priorityMap.get(10)
        // use it here
    }
}
13
Viktor Stoitschev 18 nov. 2017 a las 18:41

Desde Spring 4.1.x (aunque no recuerdo la versión específica), puedes hacer algo como

@Value("#{${your.properties.key.name}}")
private Map<String, String> myMap;

Donde your.properties.key.name en su archivo de propiedades debería ser algo como

your.properties.key.name={\
    name1 : 100, \
    name2 : 200 \
}

Solo asegúrese de crear el bean PropertySourcesPlaceholderConfigurer para que funcione tanto en su aplicación como si está escribiendo un código de prueba unitario para probar su código; de lo contrario, el marcador de posición $ {...} para el valor de la propiedad no funcionará como se esperaba y verá algunos errores extraños de SpringEL.

17
wonhee 22 jun. 2018 a las 00:30

¿Es posible usar Spring @Value para mapear valores del archivo de propiedades al HashMap?

Sí lo es. Con un poco de ayuda de código y Spel.

En primer lugar, considere este singleton Spring-bean (debe escanearlo):

@Component("PropertySplitter")
public class PropertySplitter {

    /**
     * Example: one.example.property = KEY1:VALUE1,KEY2:VALUE2
     */
    public Map<String, String> map(String property) {
        return this.map(property, ",");
    }

    /**
     * Example: one.example.property = KEY1:VALUE1.1,VALUE1.2;KEY2:VALUE2.1,VALUE2.2
     */
    public Map<String, List<String>> mapOfList(String property) {
        Map<String, String> map = this.map(property, ";");

        Map<String, List<String>> mapOfList = new HashMap<>();
        for (Entry<String, String> entry : map.entrySet()) {
            mapOfList.put(entry.getKey(), this.list(entry.getValue()));
        }

        return mapOfList;
    }

    /**
     * Example: one.example.property = VALUE1,VALUE2,VALUE3,VALUE4
     */
    public List<String> list(String property) {
        return this.list(property, ",");
    }

    /**
     * Example: one.example.property = VALUE1.1,VALUE1.2;VALUE2.1,VALUE2.2
     */
    public List<List<String>> groupedList(String property) {
        List<String> unGroupedList = this.list(property, ";");

        List<List<String>> groupedList = new ArrayList<>();
        for (String group : unGroupedList) {
            groupedList.add(this.list(group));
        }

        return groupedList;

    }

    private List<String> list(String property, String splitter) {
        return Splitter.on(splitter).omitEmptyStrings().trimResults().splitToList(property);
    }

    private Map<String, String> map(String property, String splitter) {
        return Splitter.on(splitter).omitEmptyStrings().trimResults().withKeyValueSeparator(":").split(property);
    }

}

Nota: la clase PropertySplitter utiliza la utilidad Splitter de Guava. Consulte su documentación para obtener más detalles.

Luego, en un frijol tuyo:

@Component
public class MyBean {

    @Value("#{PropertySplitter.map('${service.expiration}')}")
    Map<String, String> propertyAsMap;

}

Y finalmente, la propiedad:

service.expiration = name1:100,name2:20

No es exactamente lo que ha preguntado, porque este PropertySplitter funciona con una sola propiedad que se transforma en un Map, pero creo que podría cambiar a esta forma de especificando propiedades, o modifique el código PropertySplitter para que coincida con la forma más jerárquica que desee.

23
fps 6 feb. 2015 a las 17:07

Puede usar la sintaxis similar a SPEL json para escribir un mapa simple o un mapa de lista en el archivo de propiedades.

simple.map={'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}

map.of.list={\
  'KEY1': {'value1','value2'}, \
  'KEY2': {'value3','value4'}, \
  'KEY3': {'value5'} \
 }

Usé \ para la propiedad de varias líneas para mejorar la legibilidad

Luego, en Java, puede acceder y analizarlo automáticamente con @Value como este.

@Value("#{${simple.map}}")
Map<String, String> simpleMap;

@Value("#{${map.of.list}}")
Map<String, List<String>> mapOfList;

Aquí, con ${simple.map}, @Value obtiene la siguiente cadena del archivo de propiedades:

"{'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}"

Luego, se evalúa como si estuviera en línea.

@Value("#{{'KEY1': 'value1', 'KEY2': 'value3', 'KEY3': 'value5'}}")

Puede obtener más información en la documentación oficial

39
Marc Bouvier 21 sep. 2018 a las 08:34