Spring:将所有 Environment 属性作为 Map 或 Properties 对象访问

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23506471/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 06:42:33  来源:igfitidea点击:

Spring: access all Environment properties as a Map or Properties object

spring

提问by RoK

I am using annotations to configure my spring environment like this:

我正在使用注释来配置我的 spring 环境,如下所示:

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

This leads to my properties from default.propertiesbeing part of the Environment. I want to use the @PropertySourcemechanism here, because it already provides the possibility to overload properties through several fallback layers and different dynamic locations, based on the environment settings (e.g. config_dir location). I just stripped the fallback to make the example easier.

这导致我的属性default.properties成为Environment. 我想在@PropertySource这里使用该机制,因为它已经提供了根据环境设置(例如 config_dir 位置)通过多个回退层和不同动态位置重载属性的可能性。我只是去掉了回退以使示例更容易。

However, my problem now is that I want to configure for example my datasource properties in default.properties. You can pass the settings to the datasource without knowing in detail what settings the datasource expects using

但是,我现在的问题是我想在default.properties. 您可以将设置传递给数据源,而无需详细了解数据源期望使用的设置

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

However, the problem is, the Environmentobject is neither a Propertiesobject nor a Mapnor anything comparable. From my point of view it is simply not possible to access all values of the environment, because there is no keySetor iteratormethod or anything comparable.

然而,问题是,Environment对象既不是Properties对象,也不是Map任何可比较的东西。从我的角度来看,根本不可能访问环境的所有值,因为没有keySetiterator方法或任何可比较的方法。

Properties p <=== Environment env?

Am I missing something? Is it possible to access all entries of the Environmentobject somehow? If yes, I could map the entries to a Mapor Propertiesobject, I could even filter or map them by prefix - create subsets as a standard java Map... This is what I would like to do. Any suggestions?

我错过了什么吗?是否可以以Environment某种方式访问对象的所有条目?如果是,我可以将条目映射到 aMapProperties对象,我什至可以通过前缀过滤或映射它们 - 创建子集作为标准 java Map......这就是我想要做的。有什么建议?

采纳答案by Andrei Stefan

You need something like this, maybe it can be improved. This is a first attempt:

你需要这样的东西,也许可以改进。这是第一次尝试:

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

Basically, everything from the Environment that's a MapPropertySource(and there are quite a lot of implementations) can be accessed as a Mapof properties.

基本上,环境中的所有内容MapPropertySource(并且有很多实现)都可以作为Map属性访问。

回答by pedorro

This is an old question, but the accepted answer has a serious flaw. If the Spring Environmentobject contains any overriding values (as described in Externalized Configuration), there is no guarantee that the map of property values it produces will match those returned from the Environmentobject. I found that simply iterating through the PropertySources of the Environmentdid not, in fact, give any overriding values. Instead it produced the original value, the one that should have been overridden.

这是一个古老的问题,但接受的答案有一个严重的缺陷。如果 SpringEnvironment对象包含任何覆盖值(如外部化配置中所述),则无法保证它生成的属性值映射将匹配从Environment对象返回的值。我发现,简单地遍历 the 的PropertySourcesEnvironment实际上并没有给出任何重要的值。相反,它产生了原本应该被覆盖的原始值。

Here is a better solution. This uses the EnumerablePropertySources of the Environmentto iterate through the known property names, but then reads the actual value out of the real Spring environment. This guarantees that the value is the one actually resolved by Spring, including any overriding values.

这是一个更好的解决方案。这使用 的EnumerablePropertySourcesEnvironment来遍历已知的属性名称,然后从真实的 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)));

回答by Heri

I had the requirement to retrieve all properties whose key starts with a distinct prefix (e.g. all properties starting with "log4j.appender.") and wrote following Code (using streams and lamdas of Java 8).

我需要检索其键以不同前缀开头的所有属性(例如,所有以“log4j.appender.”开头的属性)并编写了以下代码(使用 Java 8 的流和 lamdas)。

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

Note that the starting point is the ConfigurableEnvironment which is able to return the embedded PropertySources (the ConfigurableEnvironment is a direct descendant of Environment). You can autowire it by:

请注意,起点是 ConfigurableEnvironment,它能够返回嵌入的 PropertySources(ConfigurableEnvironment 是 Environment 的直接后代)。您可以通过以下方式自动装配:

@Autowired
private ConfigurableEnvironment  myEnv;

If you not using very special kinds of property sources (like JndiPropertySource, which is usually not used in spring autoconfiguration) you can retrieve all properties held in the environment.

如果您不使用非常特殊的属性源(如 JndiPropertySource,它通常不用于 spring 自动配置),您可以检索环境中保存的所有属性。

The implementation relies on the iteration order which spring itself provides and takes the first found property, all later found properties with the same name are discarded. This should ensure the same behaviour as if the environment were asked directly for a property (returning the first found one).

该实现依赖于 spring 本身提供的迭代顺序,并采用第一个找到的属性,所有后来找到的同名属性都将被丢弃。这应该确保与直接向环境请求属性相同的行为(返回第一个找到的属性)。

Note also that the returned properties are not yet resolved if they contain aliases with the ${...} operator. If you want to have a particular key resolved you have to ask the Environment directly again:

另请注意,如果返回的属性包含带有 ${...} 运算符的别名,则尚未解析。如果您想解析特定的密钥,您必须再次直接询问环境:

myEnv.getProperty( key );

回答by AbuNassar

The original question hinted that it would be nice to be able to filter all the properties based on a prefix. I have just confirmed that this works as of Spring Boot 2.1.1.RELEASE, for PropertiesorMap<String,String>. I'm sure it's worked for while now. Interestingly, it does not work without the prefix =qualification, i.e. I do notknow how to get the entireenvironment loaded into a map. As I said, this might actually be what OP wanted to begin with. The prefix and the following '.' will be stripped off, which might or might not be what one wants:

最初的问题暗示能够根据前缀过滤所有属性会很好。我刚刚确认这适用于 Spring Boot 2.1.1.RELEASE,对于PropertiesMap<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);
}

Postscript: It is indeed possible, and shamefully easy, to get the entire environment. I don't know how this escaped me:

后记:获取整个环境确实是可能的,而且非常容易。我不知道这是如何逃脱我的:

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

回答by jasonleakey

As this Spring's Jira ticket, it is an intentional design. But the following code works for me.

作为这个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;
}

回答by weberjn

Spring won't allow to decouple via java.util.Propertiesfrom Spring Environment.

Spring 不允许java.util.Properties从 Spring Environment解耦 via 。

But Properties.load()still works in a Spring boot application:

Properties.load()仍然适用于 Spring 启动应用程序:

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

回答by Chad Van De Hey

The other answers have pointed out the solution for the majority of cases involving PropertySources, but none have mentioned that certain property sources are unable to be casted into useful types.

其他答案指出了大多数涉及 的情况的解决方案PropertySources,但没有人提到某些属性源无法转换为有用的类型。

One such example is the property source for command line arguments. The class that is used is SimpleCommandLinePropertySource. This privateclass is returned by a publicmethod, thus making it extremely tricky to access the data inside the object. I had to use reflection in order to read the data and eventually replace the property source.

一个这样的例子是命令行参数的属性源。使用的类是SimpleCommandLinePropertySource. 这个私有类由一个公共方法返回,因此访问对象内部的数据变得非常棘手。我不得不使用反射来读取数据并最终替换属性源。

If anyone out there has a better solution, I would really like to see it; however, this is the only hack I have gotten to work.

如果有人有更好的解决方案,我真的很想看看;然而,这是我唯一开始工作的黑客。

回答by Mike

Working with Spring Boot 2, I needed to do something similar. Most of the answers above work fine, just beware that at various phases in the app lifecycles the results will be different.

使用 Spring Boot 2,我需要做一些类似的事情。上面的大多数答案都很好,只是要注意在应用程序生命周期的不同阶段,结果会有所不同。

For example, after a ApplicationEnvironmentPreparedEventany properties inside application.propertiesare not present. However, after a ApplicationPreparedEventevent they are.

例如,在 a 之后,ApplicationEnvironmentPreparedEvent里面的任何属性application.properties都不存在。然而,在一个ApplicationPreparedEvent事件之后,他们是。

回答by Jeff Brower

For Spring Boot, the accepted answer will overwrite duplicate properties with lower priorityones. This solution will collect the properties into a SortedMapand take only the highest priority duplicate properties.

对于 Spring Boot,接受的答案将覆盖优先级较低的重复属性。此解决方案会将属性收集到一个中,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);
}