Używam adnotacji, aby skonfigurować moje środowisko wiosenne w następujący sposób:
@Configuration
...
@PropertySource("classpath:/config/default.properties")
...
public class GeneralApplicationConfiguration implements WebApplicationInitializer
{
@Autowired
Environment env;
}
Prowadzi to do tego, że moje właściwości default.properties
są częścią Environment
. Chcę @PropertySource
tutaj użyć mechanizmu, ponieważ zapewnia on już możliwość przeciążania właściwości przez kilka warstw rezerwowych i różne dynamiczne lokalizacje, w oparciu o ustawienia środowiska (np. Lokalizacja config_dir). Po prostu usunąłem rezerwę, aby ułatwić przykład.
Jednak moim problemem jest teraz to, że chcę skonfigurować na przykład właściwości mojego źródła danych w default.properties
. Możesz przekazać ustawienia do źródła danych, nie wiedząc szczegółowo, jakich ustawień oczekuje źródło danych
Properties p = ...
datasource.setProperties(p);
Problem polega jednak na tym, że Environment
przedmiot nie jest ani Properties
przedmiotem, Map
ani niczym porównywalnym. Z mojego punktu widzenia jest to po prostu niemożliwe, aby uzyskać dostęp do wszystkich walorów środowiska, ponieważ nie ma keySet
ani iterator
metoda lub coś porównywalne.
Properties p <=== Environment env?
Czy coś mi brakuje? Czy można w Environment
jakiś sposób uzyskać dostęp do wszystkich wpisów obiektu? Jeśli tak, mógłbym zmapować wpisy do obiektu Map
lub Properties
obiektu, mógłbym nawet filtrować lub mapować je według prefiksu - tworzyć podzbiory jako standardowe java Map
... To właśnie chciałbym zrobić. Jakieś sugestie?
applicationContext.getEnvironment().getProperty(key)
użyłbym ich do rozwiązaniaTo stare pytanie, ale przyjęta odpowiedź ma poważną wadę. Jeśli
Environment
obiekt Spring zawiera jakiekolwiek wartości nadpisujące (zgodnie z opisem w konfiguracji zewnętrznej ), nie ma gwarancji, że mapa wartości właściwości, które generuje, będzie pasować do zwracanych zEnvironment
obiektu. Zauważyłem, że zwykłe iterowanie przezPropertySource
s z wEnvironment
rzeczywistości nie daje żadnych nadrzędnych wartości. Zamiast tego wytworzył pierwotną wartość, taką, która powinna była zostać nadpisana.Oto lepsze rozwiązanie. Używa
EnumerablePropertySource
s of theEnvironment
do iteracji przez znane nazwy właściwości, ale następnie odczytuje rzeczywistą wartość z rzeczywistego środowiska Spring. Gwarantuje to, że wartość jest faktycznie ustalona przez Spring, w tym wszelkie wartości nadrzędne.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)));
źródło
collect
metody na potoku zamiast robiforEach
:.distinct().collect(Collectors.toMap(Function.identity(), springEnv::getProperty))
. Jeśli potrzebujesz zebrać je do Właściwości zamiast mapy, możesz użyć czteroargumentowej wersjicollect
.springEnv
? Skąd to pochodzi? Czy różni się odenv
przyjętego rozwiązania?springEnv
jestenv
przedmiotem pierwotnego pytania i przyjętego rozwiązania. Przypuszczam, że powinienem zachować tę samą nazwę.ConfigurableEnvironment
i nie musisz robić obsady.Miałem wymóg, aby pobrać wszystkie właściwości, których klucz zaczyna się od wyraźnego prefiksu (np. Wszystkie właściwości zaczynające się od „log4j.appender.”) I napisałem następujący kod (używając strumieni i lamdas 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() ); } }
Zauważ, że punktem początkowym jest ConfigurableEnvironment, który jest w stanie zwrócić osadzone PropertySources (ConfigurableEnvironment jest bezpośrednim potomkiem Environment). Możesz go automatycznie podłączyć:
@Autowired private ConfigurableEnvironment myEnv;
Jeśli nie używasz bardzo specjalnych rodzajów źródeł właściwości (takich jak JndiPropertySource, który zwykle nie jest używany w wiosennej automatycznej konfiguracji), możesz pobrać wszystkie właściwości przechowywane w środowisku.
Implementacja opiera się na kolejności iteracji, która sama zapewnia sprężynę i przyjmuje pierwszą znalezioną właściwość, wszystkie później znalezione właściwości o tej samej nazwie są odrzucane. Powinno to zapewnić takie samo zachowanie, jak gdyby środowisko zostało bezpośrednio poproszone o właściwość (zwrócenie pierwszej znalezionej).
Zauważ również, że zwrócone właściwości nie są jeszcze rozwiązane, jeśli zawierają aliasy z operatorem $ {...}. Jeśli chcesz rozwiązać konkretny klucz, musisz ponownie zapytać bezpośrednio Środowisko:
myEnv.getProperty( key );
źródło
NullPointerException
w moich testach jednostkowych, gdy próbuje uzyskać@Autowired
wystąpienieConfigurationEnvironment
.Pierwotne pytanie sugerowało, że fajnie byłoby móc filtrować wszystkie właściwości na podstawie przedrostka. Właśnie potwierdziłem, że działa to od wersji Spring Boot 2.1.1.RELEASE dla
Properties
lubMap<String,String>
. Jestem pewien, że teraz to działało. Co ciekawe, bezprefix =
kwalifikacji nie działa , czyli nie wiem jak załadować całe środowisko do mapy. Jak powiedziałem, może to być coś, od czego OP chciał zacząć. Przedrostek i następujący po nim „.” zostanie pozbawiony, co może, ale nie musi być tym, czego się chce:@ConfigurationProperties(prefix = "abc") @Bean public Properties getAsProperties() { return new Properties(); } @Bean public MyService createService() { Properties properties = getAsProperties(); return new MyService(properties); }
Postscriptum: Rzeczywiście jest możliwe i haniebnie łatwe, aby uzyskać całe środowisko. Nie wiem, jak mi to umknęło:
@ConfigurationProperties @Bean public Properties getProperties() { return new Properties(); }
źródło
getAsProperties()
zawsze zwraca pustąProperties
instancję, a wypróbowanie jej bez określonego przedrostka nie pozwala nawet na kompilację. To jest z Spring Boot 2.1.6. WYDANIEJak na wiosenny bilet do Jira , jest to celowy projekt. Ale następujący kod działa dla mnie.
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; }
źródło
Wiosna nie pozwoli na odłączenie połączenia via
java.util.Properties
od Spring Environment.Ale
Properties.load()
nadal działa w aplikacji rozruchowej Spring:Properties p = new Properties(); try (InputStream is = getClass().getResourceAsStream("/my.properties")) { p.load(is); }
źródło
Inne odpowiedzi wskazywały na rozwiązanie większości spraw z udziałem
PropertySources
, ale żadna z nich nie wspomniała, że pewnych źródeł własności nie można podzielić na użyteczne typy.Jednym z takich przykładów jest źródło właściwości dla argumentów wiersza poleceń. Używana jest klasa
SimpleCommandLinePropertySource
. Ta klasa prywatna jest zwracana przez publiczną metodę , przez co dostęp do danych wewnątrz obiektu jest niezwykle trudny. Musiałem użyć odbicia, aby odczytać dane i ostatecznie zastąpić źródło właściwości.Jeśli ktoś ma lepsze rozwiązanie, naprawdę chciałbym je zobaczyć; jest to jednak jedyny hack, jaki mam do roboty.
źródło
Pracując ze Spring Boot 2, musiałem zrobić coś podobnego. Większość powyższych odpowiedzi działa dobrze, ale pamiętaj, że na różnych etapach cyklu życia aplikacji wyniki będą różne.
Na przykład po
ApplicationEnvironmentPreparedEvent
żadnej właściwości w środkuapplication.properties
nie ma. Jednak poApplicationPreparedEvent
wydarzeniu są.źródło
W przypadku Spring Boot zaakceptowana odpowiedź nadpisze zduplikowane właściwości tymi o niższym priorytecie . To rozwiązanie zbierze właściwości do
SortedMap
i przyjmie tylko zduplikowane właściwości o najwyższym priorytecie.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); }
źródło
Pomyślałem, że dodam jeszcze jeden sposób. W moim przypadku dostarczam to, do
com.hazelcast.config.XmlConfigBuilder
którego wystarczyjava.util.Properties
rozwiązać niektóre właściwości w pliku konfiguracyjnym Hazelcast XML, tj. Wywołuje tylkogetProperty(String)
metodę. To pozwoliło mi zrobić to, czego potrzebowałem:@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 * }
PS Skończyło się na tym, że nie używałem tego specjalnie dla Hazelcast, ponieważ rozwiązuje tylko właściwości pliku XML, ale nie w czasie wykonywania. Ponieważ używam również Spring, zdecydowałem się na niestandardowy
org.springframework.cache.interceptor.AbstractCacheResolver#getCacheNames
. To rozwiązuje właściwości w obu sytuacjach, przynajmniej jeśli używasz właściwości w nazwach pamięci podręcznej.źródło