web-dev-qa-db-fra.com

Comment remplir HashMap à partir de Java avec Spring @Value

Est-il possible d'utiliser Spring @Value pour mapper les valeurs d'un fichier de propriétés vers HashMap.

Actuellement, j'ai quelque chose comme ça, et mapper une valeur n'est pas un problème. Mais je dois mapper les valeurs personnalisées dans les expirations HashMap. Est-ce que quelque chose comme ça est possible?

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

Fichier de propriétés: 'my_service.properties'

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

Est-il possible de mapper comme ceci: ensemble de valeurs

  • nom1 = 100

  • nom2 = 20

34
d-sauer

Je fais une solution inspirée par le post précédent.

Enregistrer le fichier de propriétés dans la configuration de printemps:

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

Et je crée un composant:

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

Et lorsque je veux mapper toutes les propriétés commençant par une valeur spécifix vers HashMap, avec l'annotation @Value:

@Service
public class MyServiceImpl implements MyService {

    @Value("#{PropertyMapper.startWith('myProp', 'service.expiration.', true)}")
    private HashMap<String, Object> portalExpirations;
15
d-sauer

Est-il possible d'utiliser Spring @Value pour mapper les valeurs d'un fichier de propriétés vers HashMap?

Oui, ça l'est. Avec un peu d'aide de code et Spel .

Tout d'abord, considérons ce singleton Spring-bean (vous devriez le scanner):

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

}

Remarque: PropertySplitter la classe utilise l'utilitaire Splitter de Guava. Veuillez vous référer à sa documentation pour plus de détails.

Ensuite, dans votre haricot:

@Component
public class MyBean {

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

}

Et enfin, la propriété:

service.expiration = name1:100,name2:20

Ce n’est pas exactement ce que vous avez demandé, car ce PropertySplitter fonctionne avec une seule propriété transformée en un Map, mais je pense que vous pouvez soit basculer à cette manière de spécifier les propriétés, ou modifiez le code PropertySplitter afin qu'il corresponde à la manière plus hiérarchique que vous désirez.

Vous pouvez utiliser la syntaxe SPEL de type json pour écrire une mappe simple ou une mappe de liste dans un fichier de propriétés.

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

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

J'ai utilisé \ pour une propriété multiligne améliorant la lisibilité

Ensuite, en Java, vous pouvez y accéder et l’analyser automatiquement avec @Value comme ça.

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

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

Ci-joint ${simple.map}, @Value obtient la chaîne suivante du fichier de propriétés:

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

Ensuite, il est évalué comme s'il était en ligne

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

Vous pouvez en apprendre plus dans la documentation officielle

15
Marc Bouvier

A partir de Spring 4.1.x (je ne me souviens pas de la version spécifique), vous pouvez faire quelque chose comme:

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

où votre.properties.key.name dans votre fichier de propriétés devrait être quelque chose comme

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

Assurez-vous simplement que vous devez créer un bean PropertySourcesPlaceholderConfigurer pour que cela fonctionne dans votre application et si vous écrivez un code de test unitaire pour tester votre code, sinon un espace réservé $ {...} pour la valeur de la propriété ne fonctionnera pas comme prévu et vous verrez des erreurs SpringEL étranges.

10
wonhee

La solution la plus rapide à base de printemps Boot à laquelle je peux penser suit. Dans mon exemple particulier, je migre des données d'un système à un autre. C'est pourquoi j'ai besoin d'un mapping pour un champ appelé priority.

J'ai d'abord créé le fichier de propriétés (priority-migration.properties) comme ceci:

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

et le mettre sur le classpath.

En supposant que vous souhaitiez utiliser la carte dans un composant/bean géré par un ressort, annotez votre classe avec:

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

Ce que vous voulez réellement dans votre carte, ce n’est bien sûr que les paires clé/valeur qui sont préfixées par mon.prefix, c’est-à-dire cette partie:

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

Pour cela, vous devez annoter votre composant avec

@ConfigurationProperties("my.prefix")

et créez un getter pour l'infixe priorité . Ce dernier s’est avéré être obligatoire dans mon cas (bien que Sring Doc indique qu’il suffit d’avoir une propriété prioritaire et d’initialiser avec une valeur mutable)

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

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

À la fin

Cela ressemble à quelque chose comme ça:

@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
    }
}
7
Viktor Stoitschev