2015-06-25 8 views
18

Ich frage mich, ob es eine generische Möglichkeit gibt, eine Karte mit Eigenschaften zu füllen, die Sie nur kennen, das Präfix.Verwenden von ConfigurationProperties zum allgemeinen Füllen von Map

Angenommen, es gibt eine Reihe von Eigenschaften wie

namespace.prop1=value1 
namespace.prop2=value2 
namespace.iDontKnowThisNameAtCompileTime=anothervalue 

Ich mag würde eine generische Art und Weise haben diese Eigenschaft in einer Karte zu füllen, so etwas wie

@Component 
@ConfigurationProperties("namespace") 
public class MyGenericProps { 
    private Map<String, String> propmap = new HashMap<String, String>(); 

    // setter and getter for propmap omitted 

    public Set<String> returnAllKeys() { 
     return propmap.keySet(); 
    } 
} 

Oder gibt es eine andere bequeme Möglichkeit, alle Eigenschaften mit einem bestimmten Präfix zu sammeln, anstatt über alle PropertySources in der Umgebung zu iterieren?

Dank Hansjörg

Antwort

30

Solange Sie glücklich sind jede Eigenschaft in der Karte hinzugefügt haben, und nicht nur diejenigen, die Sie vorher nicht wissen, können Sie dies mit @ConfigurationProperties tun können. Wenn Sie alles packen wollen, die unter namespace ist, dann müssen Sie ein leeres Präfix verwenden und einen Getter für eine Karte namespace genannt bieten:

@ConfigurationProperties("") 
public class CustomProperties { 

    private final Map<String, String> namespace = new HashMap<>(); 

    public Map<String, String> getNamespace() { 
     return namespace; 
    } 

} 

Frühlings-Boot verwendet die getNamespace Methode, um die Karte zu holen, so dass es das Video! Eigenschaften dazu. Mit diesen Eigenschaften:

namespace.a=alpha 
namespace.b=bravo 
namespace.c=charlie 

Die namespace Karte drei Einträge enthalten:

{a=alpha, b=bravo, c=charlie} 

Wenn die Eigenschaften tiefer verschachtelt wurden, zum Beispiel:

namespace.foo.bar.a=alpha 
namespace.foo.bar.b=bravo 
namespace.foo.bar.c=charlie 

Dann würden Sie verwenden namespace.foo als Präfix und umbenennen namespace und getNamespace auf CustomProperties zu bar und getBar bzw..

Beachten Sie, dass Sie @EnableConfigurationProperties auf Ihre Konfiguration anwenden sollten, um die Unterstützung für @ConfigurationProperties zu aktivieren. Sie können dann alle Bohnen verweisen, die Sie mit, dass Anmerkung verarbeitet werden soll, anstatt eine @Bean Methode für sie bereitstellt, oder @Component mit ihnen durch die Komponente Scannen entdeckt zu haben:

@SpringBootApplication 
@EnableConfigurationProperties(CustomProperties.class) 
public class YourApplication { 
    // … 
} 
+0

Können Sie erweitern auf, warum wir shouldn‘ t @ConfigurationProperties mit @Component annotieren? Es ist in den Boot-Dokumenten gemacht.Wollen Sie sagen, dass die Präferenz @EnableConfiguratinoProperties ist? – jst

+0

Ich habe gehastet und hätte mich etwas besser erklären sollen. Was ich geschrieben habe, war zu stark formuliert. Sie sollten '@ EnableConfigurationProperties' verwenden, um die Unterstützung für' @ConfigurationProperties' annotierte Beans zu aktivieren. An diesem Punkt können Sie auf Ihre mit der @ @ ConfigurationProperties'-Annotation versehene Klasse verweisen, um Verknüpfungen zu vermeiden und sie nicht als Bean deklarieren zu müssen. Dies ist, was Boot selbst in seinem eigenen Code tut. Ich werde das bisschen meiner Antwort umschreiben. –

1

Ich schrieb mir eine MapFilter Klasse dies effizient zu handhaben. Im Wesentlichen erstellen Sie eine Map und filtern sie dann, indem Sie ein Präfix für den Schlüssel angeben. Es gibt auch einen Konstruktor, der aus Bequemlichkeit einen Properties nimmt.

Beachten Sie, dass dies nur die Hauptkarte filtert. Alle Änderungen, die auf die gefilterte Karte angewendet werden, werden auch auf die Basiskarte angewendet, einschließlich Löschungen usw., aber offensichtlich werden Änderungen an der Hauptkarte nicht in der gefilterten Karte wiedergegeben, bis etwas eine Neuerstellung verursacht.

Es ist auch sehr einfach (und effizient), bereits gefilterte Karten zu filtern.

public class MapFilter<T> implements Map<String, T> { 

    // The enclosed map -- could also be a MapFilter. 
    final private Map<String, T> map; 
    // Use a TreeMap for predictable iteration order. 
    // Store Map.Entry to reflect changes down into the underlying map. 
    // The Key is the shortened string. The entry.key is the full string. 
    final private Map<String, Map.Entry<String, T>> entries = new TreeMap<>(); 
    // The prefix they are looking for in this map. 
    final private String prefix; 

    public MapFilter(Map<String, T> map, String prefix) { 
     // Store my backing map. 
     this.map = map; 
     // Record my prefix. 
     this.prefix = prefix; 
     // Build my entries. 
     rebuildEntries(); 
    } 

    public MapFilter(Map<String, T> map) { 
     this(map, ""); 
    } 

    private synchronized void rebuildEntries() { 
     // Start empty. 
     entries.clear(); 
     // Build my entry set. 
     for (Map.Entry<String, T> e : map.entrySet()) { 
      String key = e.getKey(); 
      // Retain each one that starts with the specified prefix. 
      if (key.startsWith(prefix)) { 
       // Key it on the remainder. 
       String k = key.substring(prefix.length()); 
       // Entries k always contains the LAST occurrence if there are multiples. 
       entries.put(k, e); 
      } 
     } 

    } 

    @Override 
    public String toString() { 
     return "MapFilter (" + prefix + ") of " + map + " containing " + entrySet(); 
    } 

    // Constructor from a properties file. 
    public MapFilter(Properties p, String prefix) { 
     // Properties extends HashTable<Object,Object> so it implements Map. 
     // I need Map<String,T> so I wrap it in a HashMap for simplicity. 
     // Java-8 breaks if we use diamond inference. 
     this(new HashMap<String, T>((Map) p), prefix); 
    } 

    // Helper to fast filter the map. 
    public MapFilter<T> filter(String prefix) { 
     // Wrap me in a new filter. 
     return new MapFilter<>(this, prefix); 
    } 

    // Count my entries. 
    @Override 
    public int size() { 
     return entries.size(); 
    } 

    // Are we empty. 
    @Override 
    public boolean isEmpty() { 
     return entries.isEmpty(); 
    } 

    // Is this key in me? 
    @Override 
    public boolean containsKey(Object key) { 
     return entries.containsKey(key); 
    } 

    // Is this value in me. 
    @Override 
    public boolean containsValue(Object value) { 
     // Walk the values. 
     for (Map.Entry<String, T> e : entries.values()) { 
      if (value.equals(e.getValue())) { 
       // Its there! 
       return true; 
      } 
     } 
     return false; 
    } 

    // Get the referenced value - if present. 
    @Override 
    public T get(Object key) { 
     return get(key, null); 
    } 

    // Get the referenced value - if present. 
    public T get(Object key, T dflt) { 
     Map.Entry<String, T> e = entries.get((String) key); 
     return e != null ? e.getValue() : dflt; 
    } 

    // Add to the underlying map. 
    @Override 
    public T put(String key, T value) { 
     T old = null; 
     // Do I have an entry for it already? 
     Map.Entry<String, T> entry = entries.get(key); 
     // Was it already there? 
     if (entry != null) { 
      // Yes. Just update it. 
      old = entry.setValue(value); 
     } else { 
      // Add it to the map. 
      map.put(prefix + key, value); 
      // Rebuild. 
      rebuildEntries(); 
     } 
     return old; 
    } 

    // Get rid of that one. 
    @Override 
    public T remove(Object key) { 
     // Do I have an entry for it? 
     Map.Entry<String, T> entry = entries.get((String) key); 
     if (entry != null) { 
      entries.remove(key); 
      // Change the underlying map. 
      return map.remove(prefix + key); 
     } 
     return null; 
    } 

    // Add all of them. 
    @Override 
    public void putAll(Map<? extends String, ? extends T> m) { 
     for (Map.Entry<? extends String, ? extends T> e : m.entrySet()) { 
      put(e.getKey(), e.getValue()); 
     } 
    } 

    // Clear everything out. 
    @Override 
    public void clear() { 
     // Just remove mine. 
     // This does not clear the underlying map - perhaps it should remove the filtered entries. 
     for (String key : entries.keySet()) { 
      map.remove(prefix + key); 
     } 
     entries.clear(); 
    } 

    @Override 
    public Set<String> keySet() { 
     return entries.keySet(); 
    } 

    @Override 
    public Collection<T> values() { 
     // Roll them all out into a new ArrayList. 
     List<T> values = new ArrayList<>(); 
     for (Map.Entry<String, T> v : entries.values()) { 
      values.add(v.getValue()); 
     } 
     return values; 
    } 

    @Override 
    public Set<Map.Entry<String, T>> entrySet() { 
     // Roll them all out into a new TreeSet. 
     Set<Map.Entry<String, T>> entrySet = new TreeSet<>(); 
     for (Map.Entry<String, Map.Entry<String, T>> v : entries.entrySet()) { 
      entrySet.add(new Entry<>(v)); 
     } 
     return entrySet; 
    } 

    /** 
    * An entry. 
    * 
    * @param <T> 
    * 
    * The type of the value. 
    */ 
    private static class Entry<T> implements Map.Entry<String, T>, Comparable<Entry<T>> { 

     // Note that entry in the entry is an entry in the underlying map. 
     private final Map.Entry<String, Map.Entry<String, T>> entry; 

     Entry(Map.Entry<String, Map.Entry<String, T>> entry) { 
      this.entry = entry; 
     } 

     @Override 
     public String getKey() { 
      return entry.getKey(); 
     } 

     @Override 
     public T getValue() { 
      // Remember that the value is the entry in the underlying map. 
      return entry.getValue().getValue(); 
     } 

     @Override 
     public T setValue(T newValue) { 
      // Remember that the value is the entry in the underlying map. 
      return entry.getValue().setValue(newValue); 
     } 

     @Override 
     public boolean equals(Object o) { 
      if (!(o instanceof Entry)) { 
       return false; 
      } 
      Entry e = (Entry) o; 
      return getKey().equals(e.getKey()) && getValue().equals(e.getValue()); 
     } 

     @Override 
     public int hashCode() { 
      return getKey().hashCode()^getValue().hashCode(); 
     } 

     @Override 
     public String toString() { 
      return getKey() + "=" + getValue(); 
     } 

     @Override 
     public int compareTo(Entry<T> o) { 
      return getKey().compareTo(o.getKey()); 
     } 
    } 

    // Simple tests. 
    public static void main(String[] args) { 
     String[] samples = { 
      "Some.For.Me", 
      "Some.For.You", 
      "Some.More", 
      "Yet.More"}; 
     Map map = new HashMap(); 
     for (String s : samples) { 
      map.put(s, s); 
     } 
     Map all = new MapFilter(map); 
     Map some = new MapFilter(map, "Some."); 
     Map someFor = new MapFilter(some, "For."); 
     System.out.println("All: " + all); 
     System.out.println("Some: " + some); 
     System.out.println("Some.For: " + someFor); 
    } 
} 
Verwandte Themen