Spring: отримати доступ до всіх властивостей середовища як об'єкт Map або Properties


84

Я використовую анотації для налаштування мого весняного середовища так:

@Configuration
...
@PropertySource("classpath:/config/default.properties")
...
public class GeneralApplicationConfiguration implements WebApplicationInitializer 
{
    @Autowired
    Environment env;
}

Це призводить до того, що мої властивості не default.propertiesє частиною Environment. Я хочу використати @PropertySourceтут механізм, оскільки він уже надає можливість перевантажувати властивості через кілька резервних шарів та різні динамічні розташування, виходячи з налаштувань середовища (наприклад, розташування config_dir). Я просто позбавив запасний варіант, щоб полегшити приклад.

Однак моя проблема зараз полягає в тому, що я хочу налаштувати, наприклад, свої властивості джерела даних у default.properties. Ви можете передати налаштування джерелу даних, не знаючи детально, які параметри джерело даних очікує використовувати

Properties p = ...
datasource.setProperties(p);

Однак проблема полягає в тому, що Environmentоб'єкт не є ні Propertiesоб'єктом, ні об'єктом Mapпорівняння. З моєї точки зору , це просто не можливо отримати доступ до всіх значень середовища, оскільки не існує keySetабо iteratorметод або що - небудь порівнянне.

Properties p <=== Environment env?

Мені чогось не вистачає? Чи можна Environmentякось отримати доступ до всіх записів об’єкта? Якщо так, я міг би зіставити записи з об'єктом Mapчи Propertiesоб'єктом, я навіть міг би їх відфільтрувати або зіставити за префіксом - створювати підмножини як стандартну java Map... Це те, що я хотів би зробити. Будь-які пропозиції?

Відповіді:


72

Вам потрібно щось подібне, можливо, це можна вдосконалити. Це перша спроба:

...
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
...

@Configuration
...
@org.springframework.context.annotation.PropertySource("classpath:/config/default.properties")
...
public class GeneralApplicationConfiguration implements WebApplicationInitializer 
{
    @Autowired
    Environment env;

    public void someMethod() {
        ...
        Map<String, Object> map = new HashMap();
        for(Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator(); it.hasNext(); ) {
            PropertySource propertySource = (PropertySource) it.next();
            if (propertySource instanceof MapPropertySource) {
                map.putAll(((MapPropertySource) propertySource).getSource());
            }
        }
        ...
    }
...

В основному, все із середовища, яке є MapPropertySource(а реалізацій існує досить багато), можна отримати як Mapвластивості.


Дякуємо, що поділилися цим підходом. Я вважаю це трохи "брудним", але це, мабуть, єдиний спосіб піти сюди. Ще одним підходом, який мені показав колега, було б введення властивості в конфігурацію за допомогою фіксованого ключа, який містить список із усіма ключами властивостей. Потім ви можете прочитати властивості в об'єкті Map / Properties на основі списку ключів. Це принаймні завадило б кидати ...
RoK

20
Примітка для завантаження Spring ..., що getPropertySources () повертає PropertySource у порядку черговості, тому вам фактично потрібно змінити це у випадках, коли значення властивостей перезаписані
Роб Байгрейв

2
Як @RobBygrave згадав, порядок може бути іншим, але замість того, щоб скасувати порядок (оскільки ви можете розгорнути весняне завантаження в контейнері як війна, або ця поведінка може змінитися в майбутньому), я б просто зібрав усі ключі, а потім використовував applicationContext.getEnvironment().getProperty(key)для їх розв'язання
potato

@ картопля Це гарна ідея, і я спробував це. Єдина потенційна проблема полягає в тому, що ви стикаєтесь із проблемами оцінки із заповнювачами, як у цьому питанні тут: stackoverflow.com/questions/34584498 / ...
bischoje

1
Дякую! .. Я шукав весняну альтернативу для використання замість org.apache.ibatis.io.Resources.getResourceAsProperties ("Filepath") Це рішення дуже добре для мене спрацювало.
so-random-dude

67

Це старе запитання, але прийнята відповідь має серйозний недолік. Якщо Environmentоб'єкт Spring містить будь-які перевизначуючі значення (як описано в Зовнішній конфігурації ), немає гарантії, що карта значень властивостей, яку він створює, збігатиметься з тими, що повертаються з Environmentоб'єкта. Я виявив, що просто ітерація через PropertySources з Environment, насправді, не дала жодних переважних значень. Натомість це дало початкове значення, те, яке слід було б перевизначити.

Ось краще рішення. Це використовує EnumerablePropertySources Environmentдля перебору відомих імен властивостей, але потім зчитує фактичне значення з реального середовища Spring. Це гарантує, що значення є тим, яке насправді вирішено Spring, включаючи будь-які перевизначені значення.

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

1
Варто зазначити, що станом на весну 4.1.2 це рішення (на відміну від інших відповідей) не потребує оновлення, щоб явно мати справу з CompositePropertySource, оскільки CompositePropertySource розширює EnumerablePropertySource і, отже, getPropertyNames поверне набір усіх імен властивостей у складі джерело.
М. Джастін

5
Можна також зібрати властивості , використовуючи вбудований collectметод на потоці замість того , щоб робити forEach: .distinct().collect(Collectors.toMap(Function.identity(), springEnv::getProperty)). Якщо вам потрібно було зібрати його у Властивості, а не в Карту, ви можете скористатися чотири аргументами collect.
M. Justin

2
Що springEnv? Звідки це береться? Чи відрізняється воно від envприйнятого рішення?
sebnukem

2
@sebnukem Хороший момент. springEnvє envоб’єктом оригінального питання та прийнятого рішення. Гадаю, мені слід було зберегти ім’я таким же.
pedorro

3
Ви можете використовувати ConfigurableEnvironment і не робити акторський склад.
Абхіджіт Саркар

19

У мене була вимога отримати всі властивості, ключ яких починається з окремого префікса (наприклад, усі властивості, що починаються з "log4j.appender."), І написав наступний код (використовуючи потоки та 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() );
    }
}

Зверніть увагу, що початковою точкою є ConfigurableEnvironment, яке може повернути вбудовані PropertySources (ConfigurableEnvironment є прямим нащадком Environment). Ви можете підключити його автоматично:

@Autowired
private ConfigurableEnvironment  myEnv;

Якщо ви не використовуєте спеціальні типи джерел властивостей (наприклад, JndiPropertySource, яке зазвичай не використовується у весняній автоконфігурації), ви можете отримати всі властивості, що зберігаються в середовищі.

Реалізація покладається на порядок ітерацій, який надає сама пружина, і приймає перше знайдене властивість, усі пізніше знайдені властивості з тим самим іменем відкидаються. Це повинно забезпечити таку ж поведінку, як якщо б середовище просили безпосередньо про властивість (повернення першого знайденого).

Також зауважте, що повернені властивості ще не вирішені, якщо вони містять псевдоніми оператора $ {...}. Якщо ви хочете, щоб певний ключ був вирішений, вам доведеться запитати навколишнє середовище ще раз:

myEnv.getProperty( key );

1
Чому б просто не виявити всі ключі таким чином, а потім використати environment.getProperty, щоб змусити відповідне роздільне значення? Хотіли б переконатись, що перевизначення середовища дотримуються, наприклад application-dev.properties замінює значення за замовчуванням у application.properties і як ви вже згадали eval.
GameSalutes

На це я вказував у останньому абзаці. Використання env.getProperty забезпечує вихідну поведінку Spring.
Хері

Як ви перевіряєте це? Я завжди отримую NullPointerExceptionв своїх модульних тестах, коли він намагається отримати @Autowiredекземпляр ConfigurationEnvironment.
ArtOfWarfare

Ви впевнені, що запустили тест як весняний додаток?
Хері

Я роблю це так:
Хері

10

Початкове запитання натякало, що було б непогано мати можливість фільтрувати всі властивості на основі префікса. Я щойно підтвердив, що це працює станом на Spring Boot 2.1.1.RELEASE, для Properties або Map<String,String> . Я впевнений, що це працювало вже зараз. Цікаво, що це не працює без prefix =кваліфікації, тобто я не знаю, як отримати все середовище завантаженим на карту. Як я вже говорив, це могло насправді бути тим, з чого ОР хотів почати. Префікс та наступне '.' буде позбавлений, що може бути чи не бути тим, що хочеться:

@ConfigurationProperties(prefix = "abc")
@Bean
public Properties getAsProperties() {
    return new Properties();
}

@Bean
public MyService createService() {
    Properties properties = getAsProperties();
    return new MyService(properties);
}

Приписка: Дійсно можливо, і ганебно легко, отримати все середовище. Не знаю, як мені це вдалося:

@ConfigurationProperties
@Bean
public Properties getProperties() {
    return new Properties();
}

1
також, такі властивості, як abc = x, вкладаються в {b = {c = x}}
weberjn

Жодна частина цього не спрацювала - getAsProperties()завжди повертає порожній Propertiesекземпляр, а спроба без вказаного префіксу навіть не дозволяє компілювати. Це з Spring Boot 2.1.6. ВИПУСК
ArtOfWarfare

1
Я не пишу Java на роботі, але досить швидко це зробив : github.com/AbuCarlo/SpringPropertiesBean . Можливо, це не спрацює, якщо ви якимось чином обійдете послідовність запуску Spring (тобто компонент "властивості" ніколи не заповнюється). Це для Java 8, весна 2.2.6.
АбуНассар,

5

Оскільки цей весняний квиток на Джиру , це навмисний дизайн. Але такий код працює для мене.

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

2

Весна не дозволить відокремитись java.util.Propertiesвід Spring Environment.

Але Properties.load()все ще працює у завантажувальному додатку Spring:

Properties p = new Properties();
try (InputStream is = getClass().getResourceAsStream("/my.properties")) {
    p.load(is);
}

1

Інші відповіді вказували на вирішення більшості справ, що стосуються PropertySources, але жодна не згадувала, що певні джерела власності не можуть бути віднесені до корисних типів.

Одним з таких прикладів є джерело властивостей для аргументів командного рядка. Клас, який використовується, є SimpleCommandLinePropertySource. Цей приватний клас повертається загальнодоступним методом, що робить надзвичайно складним доступ до даних всередині об'єкта. Мені довелося використовувати відображення, щоб прочитати дані та врешті-решт замінити джерело властивостей.

Якщо хтось там має краще рішення, я б дуже хотів його бачити; однак це єдиний хакер, який я зайнявся роботою.


Ви знайшли рішення проблеми з непублічним класом?
Тобіас

1

Працюючи з Spring Boot 2, мені потрібно було зробити щось подібне. Більшість відповідей, наведених вище, працюють нормально, лише пам’ятайте, що на різних етапах життєвого циклу програми результати будуть різними.

Наприклад, після a ApplicationEnvironmentPreparedEventніяких властивостей всередині application.propertiesнемає. Однак після ApplicationPreparedEventподії вони є.


1

Для Spring Boot прийнята відповідь замінить повторювані властивості з нижчими пріоритетами . Це рішення буде збирати властивості в a SortedMapта приймати лише дублікати властивостей із найвищим пріоритетом.

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

env.getPropertySources () надає властивості від найнижчого до найвищого пріоритету?
Фараз

Це навпаки. Вони сортуються за високим -> низьким пріоритетом.
Самуель Татіпамула

0

Я хотів би додати ще один спосіб. У моєму випадку я надаю це, com.hazelcast.config.XmlConfigBuilderякому потрібно лише java.util.Propertiesвирішити деякі властивості всередині файлу конфігурації Hazelcast XML, тобто воно викликає лише getProperty(String)метод. Отже, це дозволило мені зробити те, що мені потрібно:

@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 Я в кінцевому підсумку не використовував це спеціально для Hazelcast, оскільки він вирішує лише властивості файлу XML, але не під час виконання. Оскільки я також використовую весну, я вирішив піти на замовлення org.springframework.cache.interceptor.AbstractCacheResolver#getCacheNames. Це вирішує властивості обох ситуацій, принаймні, якщо ви використовуєте властивості в іменах кешу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.