Подтвердить что ты не робот

Spring: доступ ко всем свойствам среды как к объекту Map или Properties

Я использую аннотации для настройки среды Spring следующим образом:

@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... Это то, что я хотел бы сделать. Какие-либо предложения?

4b9b3361

Ответ 1

Вам нужно что-то вроде этого, может быть, это может быть улучшено. Это первая попытка:

...
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 свойств.

Ответ 2

Это старый вопрос, но принятый ответ имеет серьезный недостаток. Если объект Spring Environment содержит любые переопределяющие значения (как описано в Внешняя настройка), нет никакой гарантии, что карта значений свойств он будет соответствовать тем, которые возвращаются из объекта Environment. Я обнаружил, что простое повторение с помощью PropertySource Environment фактически не давало никаких переопределяющих значений. Вместо этого он произвел исходное значение, которое должно было быть переопределено.

Вот лучшее решение. Это использует EnumerablePropertySource для 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)));

Ответ 3

У меня было требование получить все свойства, ключ которых начинается с отдельного префикса (например, все свойства, начинающиеся с "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 является прямым потомком среды). Вы можете автоупроверить его:

@Autowired
private ConfigurableEnvironment  myEnv;

Если вы не используете очень специальные типы источников ресурсов (например, JndiPropertySource, который обычно не используется в autoconfiguration spring), вы можете получить все свойства, хранящиеся в среде.

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

Обратите внимание, что возвращаемые свойства еще не разрешены, если они содержат псевдонимы с оператором ${...}. Если вы хотите, чтобы какой-либо конкретный ключ был разрешен, вы должны снова спросить окружающую среду:

myEnv.getProperty( key );

Ответ 4

Как этот билет Spring Jira, это намеренный дизайн. Но следующий код работает для меня.

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

Ответ 5

Исходный вопрос намекал на то, что было бы неплохо иметь возможность фильтровать все свойства на основе префикса. Я только что подтвердил, что это работает с Spring Boot 2.1.1.RELEASE, для Properties или Map<String,String>. Я уверен, что это сработало какое-то время. Интересно, что он не работает без prefix = квалификации, т.е. я не знаю, как загрузить всю среду в карту. Как я уже сказал, это может быть тем, с чего OP хотел начать. Префикс и следующий '.' будет удален, что может или не может быть тем, что вы хотите:

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

Ответ 6

В других ответах было указано решение большинства случаев, связанных с PropertySources, но никто не упомянул, что некоторые источники свойств не могут быть переведены в полезные типы.

Одним из таких примеров является источник свойств аргументов командной строки. Класс, который используется, - SimpleCommandLinePropertySource. Этот частный класс возвращается общедоступным методом, что делает его чрезвычайно сложным для доступа к данным внутри объекта. Я должен был использовать рефлексию, чтобы читать данные и в конечном итоге заменить источник собственности.

Если у кого-то есть лучшее решение, я бы очень хотел это увидеть; однако, это единственный взлом, который я получил на работу.

Ответ 7

Spring не позволяет отделить через java.util.Properties от Spring Environment.

Но Properties.load() прежнему работает в загрузочном приложении Spring:

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

Ответ 8

Попробуйте использовать следующий код:

...
@Autowired
private Environment env;
...
for(Iterator<PropertySource<?>> it = ((AbstractEnvironment) env).getPropertySources().iterator(); it.hasNext(); ) {
    PropertySource<?> propertySource = (PropertySource<?>) it.next();
    if (propertySource instanceof CompositePropertySource) {
        for(Iterator<PropertySource<?>> it2 = ((CompositePropertySource) propertySource).getPropertySources().iterator(); it2.hasNext(); ) {
            PropertySource<?> propertySource2 = (PropertySource<?>) it2.next();
            if (propertySource2 instanceof ResourcePropertySource) {
                for (Entry<String, Object> entry : ((ResourcePropertySource)propertySource2).getSource().entrySet()) {
                    if (entry.getValue() instanceof String) { 
                        System.out.println(entry.getKey() + "=" + (String)entry.getValue());
                    }
                }
            }
        }
    }
}

Ответ 9

Работая с Spring Boot 2, мне нужно было сделать что-то подобное. Большинство ответов выше работают отлично, просто остерегайтесь, что на разных этапах жизненного цикла приложения результаты будут разными.

Например, после ApplicationEnvironmentPreparedEvent никаких свойств внутри application.properties нет. Однако после события ApplicationPreparedEvent они есть.

Ответ 10

Для Spring Boot принятый ответ заменит дублированные свойства с более низкими приоритетами. Это решение будет собирать свойства в SortedMap и принимать только дубликаты свойств с наивысшим приоритетом.

final SortedMap<String, String> sortedMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (final PropertySource<?> propertySource : env.getPropertySources()) {
    if (!(propertySource instanceof EnumerablePropertySource))
        continue;
    for (final String name : ((EnumerablePropertySource<?>) propertySource).getPropertyNames())
        sortedMap.computeIfAbsent(name, propertySource::getProperty);
}