Ich hätte gerne eine generische Möglichkeit, 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 alle PropertySources in der Umgebung zu durchlaufen?
Danke Hansjörg
Andy Wilkinson
Solange Sie damit zufrieden sind, dass jede Immobilie der Karte hinzugefügt wurde, und nicht nur die, die Sie nicht im Voraus kennen, können Sie dies tun @ConfigurationProperties. Wenn Sie alles greifen wollen, was darunter ist namespace dann müssen Sie ein leeres Präfix verwenden und einen Getter für eine benannte Karte bereitstellen namespace:
@ConfigurationProperties("")
public class CustomProperties {
private final Map<String, String> namespace = new HashMap<>();
public Map<String, String> getNamespace() {
return namespace;
}
}
Spring Boot verwendet die getNamespace -Methode, um die Karte abzurufen, damit sie ihr die Eigenschaften hinzufügen kann. Mit diesen Eigenschaften:
Dann würden Sie verwenden namespace.foo als Präfix und umbenennen namespace und getNamespace an CustomProperties zu bar und getBar beziehungsweise.
Beachten Sie, dass Sie sich bewerben sollten @EnableConfigurationProperties zu Ihrer Konfiguration, um die Unterstützung für zu aktivieren @ConfigurationProperties. Sie können dann auf alle Beans verweisen, die Sie mit dieser Annotation verarbeiten möchten, anstatt eine anzugeben @Bean Methode für sie, oder mit @Component um sie durch Komponenten-Scanning entdecken zu lassen:
@SpringBootApplication
@EnableConfigurationProperties(CustomProperties.class)
public class YourApplication {
// …
}
Können Sie erläutern, warum wir @ConfigurationProperties nicht mit @Component annotieren sollten? Es ist in den Boot-Dokumenten getan. Wollen Sie damit sagen, dass die Verwendung von @EnableConfiguratinoProperties bevorzugt wird?
– jst
25. Juni 2015 um 15:25 Uhr
Ich war in Eile und hätte mich etwas besser erklären sollen. Was ich geschrieben habe, war zu stark formuliert. Du solltest benutzen @EnableConfigurationProperties um die Unterstützung für einzuschalten @ConfigurationProperties-beschriftete Bohnen. An diesem Punkt können Sie auf Ihre verweisen @ConfigurationProperties-annotierte Klasse, um Dinge zu verkürzen und zu vermeiden, dass sie als Bohne deklariert werden müssen. Dies ist, was Boot selbst in seinem eigenen Code tut. Ich werde diesen Teil meiner Antwort umformulieren.
– Andy Wilkinson
25. Juni 2015 um 16:35 Uhr
Sieht nicht so aus, als ob es für verschachtelte Eigenschaften funktioniert. metrics.tagsAsSuffix.jvm.memory=area,id mit Präfix metrics.tagsAsSuffix wird nicht eingelesen Map<String, List<String>> wie jvm.memory=[area,id] aber wird jvm=[]
– Abhijit Sarkar
9. Juni 2018 um 23:16 Uhr
snakeyaml verwendet intern LinkedHashMap (es könnte besser für Namensräume geeignet sein)
– vmartin
23. Juli 2018 um 11:39 Uhr
Vielen Dank, ich wusste nicht, dass der Name der Getter-Methode wichtig ist, ich dachte, nur der Name der Member-Variablen wäre wichtig. Aber es macht Sinn, da es eine Instanz des Map-Objekts erhält und dann etwas hinzufügt.
– Matt Weiler
15. Februar 2019 um 20:52 Uhr
Außerdem war mein Problem, dass ich nicht mehrere einfache Schlüssel/Wert-Eigenschaften hatte, sondern ganze Objekte:
Nach Jakes Rat habe ich versucht, eine Karte mit einem Pojo wie folgt zu verwenden:
@ConfigurationProperties("zuul")
public class RouteConfig {
private Map<String, Route> routes = new HashMap<>();
public Map<String, Route> getRoutes() {
return routes;
}
public static class Route {
private String path;
private boolean stripPrefix;
String url;
// [getters + setters]
}
}
Funktioniert wie ein Zauber, danke!
Hinweis: Es ist absolut entscheidend, Getter und Setter für die Felder zu haben, die Sie aus den Eigenschaften füllen möchten. Selbst wenn die Felder öffentlich sind, füllt Spring sie nicht, wenn sie keine Setter haben (oder einen Getter, im Fall einer Karte, nehme ich an). Eine Sache, die ich immer noch nicht verstehe, ist, wie Spring ableiten kann, welche Klasse die Kartenwerte haben sollten (in diesem Fall: Route), da ich immer verstanden habe, dass generische Typinformationen zur Laufzeit nicht verfügbar sind, aber irgendwie funktioniert es. Magie, schätze ich.
– Rest
25. Februar 2021 um 13:31 Uhr
@rem – Einige generische Informationen sind zur Laufzeit für die Deklaration von Klassen, Feldern und Methodeninformationen (Methodenargumente und Rückgabewert) verfügbar. siehe Beispiel tutorials.jenkov.com/java-reflection/generics.html
– YanivJ
1. März 2021 um 17:38 Uhr
Wichtig! Wenn Sie keinen Getter für Map haben (in diesem Fall getRoutes), werden die Werte nicht festgelegt.
– cdalxndr
15. April um 10:28 Uhr
Ich wurde verrückt, als ich versuchte zu verstehen, warum @ Andys Antwort bei mir nicht funktionierte (wie in, the Map blieb leer), nur um festzustellen, dass ich Lomboks hatte @Builder Anmerkung, die im Weg stand, wodurch ein nicht leerer Konstruktor hinzugefügt wurde. Ich füge diese Antwort hinzu, um dies zu betonen @ConfigurationProperties an etwas arbeiten Map, muss der Werttyp über einen No-Arguments-Konstruktor verfügen. Dies wird auch in Spring’s erwähnt Dokumentation:
Eine solche Anordnung basiert auf einem leeren Standardkonstruktor, und Getter und Setter sind normalerweise obligatorisch …
Ich hoffe, das wird jemand anderem Zeit ersparen.
Alter Geizhals
Ich schrieb mir a MapFilter Klasse, damit effizient umzugehen. Im Wesentlichen erstellen Sie eine Map und filtern Sie es dann, indem Sie ein Präfix für den Schlüssel angeben. Es gibt auch einen Konstruktor, der a akzeptiert Properties zur Bequemlichkeit.
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 widergespiegelt, bis etwas einen Neuaufbau 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);
}
}
Tsvetoslav Tsvetkov
Die meisten Orte, die ich durchquere, sind nicht vollständig beschreibend oder etwas veraltet, also sind hier die vollständigen Schritte, die ich unternommen habe, um dies zu tun. Hinweis: Die von mir verwendete Spring Boot-Version ist 2.4.0:
Fügen Sie pom.xml – spring-boot-configuration-processor hinzu
Erstellen Sie eine Eigenschaftendatei und legen Sie darin Daten fest – shared-messages.properties Notiz: “BIBLIOTHEK_10001” ist der Kartenschlüssel, “Buch kann nicht gefunden werden” ist der Kartenwert
shared-messages.messages.LIBRARY_10001=Unable to find the book
shared-messages.messages.LIBRARY_10002=Book already exists
Erstellen Sie eine Properties-Klasse, die die Properties-Datei verwendet
@ConfigurationProperties("shared-messages")
@Getter
public class LibraryProperties {
private final Map<String, String> messages = new HashMap<>();
}
Definieren Sie auf Anwendungsebene die Eigenschaftsquelle und Konfigurationseigenschaft aktivieren
@EnableConfigurationProperties(LibraryProperties.class)
@PropertySource("shared-messages.properties")
public class LibraryApplication {
....
}
Inject auf der Serviceebene “Bibliothekseigenschaften” Klasse und greifen Sie auf die Eigenschaft zu, die Sie benötigen
Es ist vielleicht nicht die perfekte Lösung, aber ich teilte mit, wie ich das geschafft habe, weil ich verschiedene Kombinationen ausprobiert habe, die für mich nicht funktionierten
11453100cookie-checkVerwenden von ConfigurationProperties zum generischen Füllen von Mapsyes